1 file changed
@@ -54,6 +54,7 @@ | |||
| 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