@@ -183,12 +183,28 @@ def get(self, name, extension=None):
183183 return self .templates [src ]
184184
185185 # compile the template
186- self . templates [ src ] = pt = RoundupPageTemplate ()
186+ pt = RoundupPageTemplate ()
187187 # use pt_edit so we can pass the content_type guess too
188188 content_type = mimetypes .guess_type (filename )[0 ] or 'text/html'
189189 pt .pt_edit (open (src ).read (), content_type )
190190 pt .id = filename
191191 pt .mtime = stime
192+ # Add it to the cache. We cannot do this until the template
193+ # is fully initialized, as we could otherwise have a race
194+ # condition when running with multiple threads:
195+ #
196+ # 1. Thread A notices the template is not in the cache,
197+ # adds it, but has not yet set "mtime".
198+ #
199+ # 2. Thread B notices the template is in the cache, checks
200+ # "mtime" (above) and crashes.
201+ #
202+ # Since Python dictionary access is atomic, as long as we
203+ # insert "pt" only after it is fully initialized, we avoid
204+ # this race condition. It's possible that two separate
205+ # threads will both do the work of initializing the template,
206+ # but the risk of wasted work is offset by avoiding a lock.
207+ self .templates [src ] = pt
192208 return pt
193209
194210 def __getitem__ (self , name ):
0 commit comments