54
54
import tempfile
55
55
import os
56
56
import sys
57
+ import time
57
58
58
59
59
60
__all__ = ('LooseObjectDB' , )
@@ -205,7 +206,7 @@ def store(self, istream):
205
206
# END assure target stream is closed
206
207
except :
207
208
if tmp_path :
208
- os . remove (tmp_path )
209
+ remove (tmp_path )
209
210
raise
210
211
# END assure tmpfile removal on error
211
212
@@ -228,9 +229,25 @@ def store(self, istream):
228
229
rename (tmp_path , obj_path )
229
230
# end rename only if needed
230
231
231
- # make sure its readable for all ! It started out as rw-- tmp file
232
- # but needs to be rwrr
233
- chmod (obj_path , self .new_objects_mode )
232
+ # Ensure rename is actually done and file is stable
233
+ # Retry up to 14 times - exponential wait & retry in ms.
234
+ # The total maximum wait time is 1000ms, which should be vastly enough for the
235
+ # OS to return and commit the file to disk.
236
+ for exp_backoff_ms in [1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 , 100 , 121 , 144 , 169 , 181 ]:
237
+ with suppress (PermissionError ):
238
+ # make sure its readable for all ! It started out as rw-- tmp file
239
+ # but needs to be rwrr
240
+ chmod (obj_path , self .new_objects_mode )
241
+ break
242
+ time .sleep (exp_backoff_ms / 1000.0 )
243
+ else :
244
+ raise PermissionError (
245
+ "Impossible to apply `chmod` to file {}" .format (obj_path )
246
+ )
247
+
248
+ # Cleanup
249
+ with suppress (FileNotFoundError ):
250
+ remove (tmp_path )
234
251
# END handle dry_run
235
252
236
253
istream .binsha = hex_to_bin (hexsha )
0 commit comments