@@ -79,7 +79,7 @@ def load_bytecode(self, f: t.BinaryIO) -> None:
79
79
self .reset ()
80
80
return
81
81
82
- def write_bytecode (self , f : t .BinaryIO ) -> None :
82
+ def write_bytecode (self , f : t .IO [ bytes ] ) -> None :
83
83
"""Dump the bytecode into the file or file like object passed."""
84
84
if self .code is None :
85
85
raise TypeError ("can't write empty bucket" )
@@ -262,13 +262,55 @@ def _get_cache_filename(self, bucket: Bucket) -> str:
262
262
def load_bytecode (self , bucket : Bucket ) -> None :
263
263
filename = self ._get_cache_filename (bucket )
264
264
265
- if os .path .exists (filename ):
266
- with open (filename , "rb" ) as f :
267
- bucket .load_bytecode (f )
265
+ # Don't test for existence before opening the file, since the
266
+ # file could disappear after the test before the open.
267
+ try :
268
+ f = open (filename , "rb" )
269
+ except (FileNotFoundError , IsADirectoryError , PermissionError ):
270
+ # PermissionError can occur on Windows when an operation is
271
+ # in progress, such as calling clear().
272
+ return
273
+
274
+ with f :
275
+ bucket .load_bytecode (f )
268
276
269
277
def dump_bytecode (self , bucket : Bucket ) -> None :
270
- with open (self ._get_cache_filename (bucket ), "wb" ) as f :
271
- bucket .write_bytecode (f )
278
+ # Write to a temporary file, then rename to the real name after
279
+ # writing. This avoids another process reading the file before
280
+ # it is fully written.
281
+ name = self ._get_cache_filename (bucket )
282
+ f = tempfile .NamedTemporaryFile (
283
+ mode = "wb" ,
284
+ dir = os .path .dirname (name ),
285
+ prefix = os .path .basename (name ),
286
+ suffix = ".tmp" ,
287
+ delete = False ,
288
+ )
289
+
290
+ def remove_silent () -> None :
291
+ try :
292
+ os .remove (f .name )
293
+ except OSError :
294
+ # Another process may have called clear(). On Windows,
295
+ # another program may be holding the file open.
296
+ pass
297
+
298
+ try :
299
+ with f :
300
+ bucket .write_bytecode (f )
301
+ except BaseException :
302
+ remove_silent ()
303
+ raise
304
+
305
+ try :
306
+ os .replace (f .name , name )
307
+ except OSError :
308
+ # Another process may have called clear(). On Windows,
309
+ # another program may be holding the file open.
310
+ remove_silent ()
311
+ except BaseException :
312
+ remove_silent ()
313
+ raise
272
314
273
315
def clear (self ) -> None :
274
316
# imported lazily here because google app-engine doesn't support
0 commit comments