summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Engelbrecht <engeg@google.com>2020-04-02 12:36:09 -0600
committerMike Frysinger <vapier@google.com>2020-04-02 21:17:54 +0000
commit9bc283e49bcb2663dc8c06a4efad289a3683aaa4 (patch)
tree7ef2de8ce8b076a07704542c8f88597dea45db6a
parentb4a6f6d7981fb6f4861485381b0b5ee761ab3ae8 (diff)
downloadgit-repo-9bc283e49bcb2663dc8c06a4efad289a3683aaa4.tar.gz
sync: add retry to fetch operations
Add retries with exponential backoff and jitter to the fetch operations. By default don't change behavior and enable behind the new flag '--fetch-retries'. Bug: https://crbug.com/1061473 Change-Id: I492710843985d00f81cbe3402dc56f2d21a45b35 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/261576 Reviewed-by: Mike Frysinger <vapier@google.com> Tested-by: George Engelbrecht <engeg@google.com>
-rw-r--r--project.py50
-rw-r--r--subcmds/sync.py5
2 files changed, 46 insertions, 9 deletions
diff --git a/project.py b/project.py
index d35ad52d..691e0d9b 100644
--- a/project.py
+++ b/project.py
@@ -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.
59MAXIMUM_RETRY_SLEEP_SEC = 3600.0
60# +-10% random jitter is added to each Fetches retry sleep duration.
61RETRY_JITTER_PERCENT = 0.1
62
63
58def _lwrite(path, content): 64def _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()
diff --git a/subcmds/sync.py b/subcmds/sync.py
index de6deecb..efd39616 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -265,6 +265,9 @@ later is required to fix a server side protocol bug.
265 p.add_option('--optimized-fetch', 265 p.add_option('--optimized-fetch',
266 dest='optimized_fetch', action='store_true', 266 dest='optimized_fetch', action='store_true',
267 help='only fetch projects fixed to sha1 if revision does not exist locally') 267 help='only fetch projects fixed to sha1 if revision does not exist locally')
268 p.add_option('--retry-fetches',
269 default=0, action='store', type='int',
270 help='number of times to retry fetches on transient errors')
268 p.add_option('--prune', dest='prune', action='store_true', 271 p.add_option('--prune', dest='prune', action='store_true',
269 help='delete refs that no longer exist on the remote') 272 help='delete refs that no longer exist on the remote')
270 if show_smart: 273 if show_smart:
@@ -342,6 +345,7 @@ later is required to fix a server side protocol bug.
342 clone_bundle=opt.clone_bundle, 345 clone_bundle=opt.clone_bundle,
343 tags=opt.tags, archive=self.manifest.IsArchive, 346 tags=opt.tags, archive=self.manifest.IsArchive,
344 optimized_fetch=opt.optimized_fetch, 347 optimized_fetch=opt.optimized_fetch,
348 retry_fetches=opt.retry_fetches,
345 prune=opt.prune, 349 prune=opt.prune,
346 clone_filter=clone_filter) 350 clone_filter=clone_filter)
347 self._fetch_times.Set(project, time.time() - start) 351 self._fetch_times.Set(project, time.time() - start)
@@ -777,6 +781,7 @@ later is required to fix a server side protocol bug.
777 current_branch_only=opt.current_branch_only, 781 current_branch_only=opt.current_branch_only,
778 tags=opt.tags, 782 tags=opt.tags,
779 optimized_fetch=opt.optimized_fetch, 783 optimized_fetch=opt.optimized_fetch,
784 retry_fetches=opt.retry_fetches,
780 submodules=self.manifest.HasSubmodules, 785 submodules=self.manifest.HasSubmodules,
781 clone_filter=self.manifest.CloneFilter) 786 clone_filter=self.manifest.CloneFilter)
782 finish = time.time() 787 finish = time.time()