diff options
Diffstat (limited to 'project.py')
-rw-r--r-- | project.py | 50 |
1 files changed, 41 insertions, 9 deletions
@@ -55,6 +55,12 @@ else: | |||
55 | input = raw_input # noqa: F821 | 55 | input = raw_input # noqa: F821 |
56 | 56 | ||
57 | 57 | ||
58 | # Maximum sleep time allowed during retries. | ||
59 | MAXIMUM_RETRY_SLEEP_SEC = 3600.0 | ||
60 | # +-10% random jitter is added to each Fetches retry sleep duration. | ||
61 | RETRY_JITTER_PERCENT = 0.1 | ||
62 | |||
63 | |||
58 | def _lwrite(path, content): | 64 | def _lwrite(path, content): |
59 | lock = '%s.lock' % path | 65 | lock = '%s.lock' % path |
60 | 66 | ||
@@ -875,6 +881,7 @@ class Project(object): | |||
875 | is_derived=False, | 881 | is_derived=False, |
876 | dest_branch=None, | 882 | dest_branch=None, |
877 | optimized_fetch=False, | 883 | optimized_fetch=False, |
884 | retry_fetches=0, | ||
878 | old_revision=None): | 885 | old_revision=None): |
879 | """Init a Project object. | 886 | """Init a Project object. |
880 | 887 | ||
@@ -901,6 +908,8 @@ class Project(object): | |||
901 | dest_branch: The branch to which to push changes for review by default. | 908 | dest_branch: The branch to which to push changes for review by default. |
902 | optimized_fetch: If True, when a project is set to a sha1 revision, only | 909 | optimized_fetch: If True, when a project is set to a sha1 revision, only |
903 | fetch from the remote if the sha1 is not present locally. | 910 | fetch from the remote if the sha1 is not present locally. |
911 | retry_fetches: Retry remote fetches n times upon receiving transient error | ||
912 | with exponential backoff and jitter. | ||
904 | old_revision: saved git commit id for open GITC projects. | 913 | old_revision: saved git commit id for open GITC projects. |
905 | """ | 914 | """ |
906 | self.manifest = manifest | 915 | self.manifest = manifest |
@@ -936,6 +945,7 @@ class Project(object): | |||
936 | self.use_git_worktrees = use_git_worktrees | 945 | self.use_git_worktrees = use_git_worktrees |
937 | self.is_derived = is_derived | 946 | self.is_derived = is_derived |
938 | self.optimized_fetch = optimized_fetch | 947 | self.optimized_fetch = optimized_fetch |
948 | self.retry_fetches = max(0, retry_fetches) | ||
939 | self.subprojects = [] | 949 | self.subprojects = [] |
940 | 950 | ||
941 | self.snapshots = {} | 951 | self.snapshots = {} |
@@ -1449,6 +1459,7 @@ class Project(object): | |||
1449 | tags=True, | 1459 | tags=True, |
1450 | archive=False, | 1460 | archive=False, |
1451 | optimized_fetch=False, | 1461 | optimized_fetch=False, |
1462 | retry_fetches=0, | ||
1452 | prune=False, | 1463 | prune=False, |
1453 | submodules=False, | 1464 | submodules=False, |
1454 | clone_filter=None): | 1465 | clone_filter=None): |
@@ -1532,7 +1543,7 @@ class Project(object): | |||
1532 | current_branch_only=current_branch_only, | 1543 | current_branch_only=current_branch_only, |
1533 | tags=tags, prune=prune, depth=depth, | 1544 | tags=tags, prune=prune, depth=depth, |
1534 | submodules=submodules, force_sync=force_sync, | 1545 | submodules=submodules, force_sync=force_sync, |
1535 | clone_filter=clone_filter): | 1546 | clone_filter=clone_filter, retry_fetches=retry_fetches): |
1536 | return False | 1547 | return False |
1537 | 1548 | ||
1538 | mp = self.manifest.manifestProject | 1549 | mp = self.manifest.manifestProject |
@@ -2334,8 +2345,10 @@ class Project(object): | |||
2334 | depth=None, | 2345 | depth=None, |
2335 | submodules=False, | 2346 | submodules=False, |
2336 | force_sync=False, | 2347 | force_sync=False, |
2337 | clone_filter=None): | 2348 | clone_filter=None, |
2338 | 2349 | retry_fetches=2, | |
2350 | retry_sleep_initial_sec=4.0, | ||
2351 | retry_exp_factor=2.0): | ||
2339 | is_sha1 = False | 2352 | is_sha1 = False |
2340 | tag_name = None | 2353 | tag_name = None |
2341 | # The depth should not be used when fetching to a mirror because | 2354 | # The depth should not be used when fetching to a mirror because |
@@ -2497,18 +2510,37 @@ class Project(object): | |||
2497 | 2510 | ||
2498 | cmd.extend(spec) | 2511 | cmd.extend(spec) |
2499 | 2512 | ||
2500 | ok = False | 2513 | # At least one retry minimum due to git remote prune. |
2501 | for _i in range(2): | 2514 | retry_fetches = max(retry_fetches, 2) |
2515 | retry_cur_sleep = retry_sleep_initial_sec | ||
2516 | ok = prune_tried = False | ||
2517 | for try_n in range(retry_fetches): | ||
2502 | gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy, | 2518 | gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy, |
2503 | merge_output=True, capture_stdout=quiet) | 2519 | merge_output=True, capture_stdout=quiet) |
2504 | ret = gitcmd.Wait() | 2520 | ret = gitcmd.Wait() |
2505 | if ret == 0: | 2521 | if ret == 0: |
2506 | ok = True | 2522 | ok = True |
2507 | break | 2523 | break |
2508 | # If needed, run the 'git remote prune' the first time through the loop | 2524 | |
2509 | elif (not _i and | 2525 | # Retry later due to HTTP 429 Too Many Requests. |
2510 | "error:" in gitcmd.stderr and | 2526 | elif ('error:' in gitcmd.stderr and |
2511 | "git remote prune" in gitcmd.stderr): | 2527 | 'HTTP 429' in gitcmd.stderr): |
2528 | if not quiet: | ||
2529 | print('429 received, sleeping: %s sec' % retry_cur_sleep, | ||
2530 | file=sys.stderr) | ||
2531 | time.sleep(retry_cur_sleep) | ||
2532 | retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep, | ||
2533 | MAXIMUM_RETRY_SLEEP_SEC) | ||
2534 | retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT, | ||
2535 | RETRY_JITTER_PERCENT)) | ||
2536 | continue | ||
2537 | |||
2538 | # If this is not last attempt, try 'git remote prune'. | ||
2539 | elif (try_n < retry_fetches - 1 and | ||
2540 | 'error:' in gitcmd.stderr and | ||
2541 | 'git remote prune' in gitcmd.stderr and | ||
2542 | not prune_tried): | ||
2543 | prune_tried = True | ||
2512 | prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True, | 2544 | prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True, |
2513 | ssh_proxy=ssh_proxy) | 2545 | ssh_proxy=ssh_proxy) |
2514 | ret = prunecmd.Wait() | 2546 | ret = prunecmd.Wait() |