Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(338)

Side by Side Diff: git_retry.py

Issue 401673003: Added 'git-retry' bootstrap (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Updated w/ real mission. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « git_common.py ('k') | man/html/git-retry.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import datetime
7 import logging
8 import optparse
9 import subprocess
10 import sys
11 import threading
12 import time
13
14 from git_common import GIT_EXE, GIT_TRANSIENT_ERRORS_RE
15
16
17 class TeeThread(threading.Thread):
18
19 def __init__(self, fd, out_fd, name):
20 super(TeeThread, self).__init__(name='git-retry.tee.%s' % (name,))
21 self.chunks = []
22 self.data = None
23 self.fd = fd
24 self.out_fd = out_fd
25
26 def run(self):
Peter Mayo 2014/07/22 22:34:36 why not chunks = [] and replace self.chunks with c
dnj 2014/07/22 23:16:20 Done.
27 for line in self.fd:
28 self.chunks.append(line)
29 self.out_fd.write(line)
30 self.data = ''.join(self.chunks)
31
32
33 class GitRetry(object):
34
35 logger = logging.getLogger('git-retry')
36 DEFAULT_RETRY_COUNT = 5
37
38 def __init__(self, retry_count=None, accepted_return_codes=None, delay=None,
39 delay_exponential=False):
40 self.retry_count = retry_count or self.DEFAULT_RETRY_COUNT
41 self.accepted_return_codes = accepted_return_codes or [0]
42 self.delay = delay or datetime.timedelta(0)
43 self.delay_exponential = delay_exponential
44
45 def shouldRetry(self, stderr):
46 m = GIT_TRANSIENT_ERRORS_RE.search(stderr)
47 if not m:
48 return False
49 if self.logger.isEnabledFor(logging.INFO):
50 start, end = m.span()
51 self.logger.info("Encountered known transient error: [%s]",
52 stderr[start:end])
Peter Mayo 2014/07/22 22:34:36 why not just self.logger.info("Encountered known
dnj 2014/07/22 23:16:20 I liked one call vs. 2, but it does look better. D
Peter Mayo 2014/07/22 23:39:54 The "if" can disappear now too. Taking the side-ef
53 return True
54
55 def execute(self, *args):
56 args = (GIT_EXE,) + args
57 proc = subprocess.Popen(
58 args,
Peter Mayo 2014/07/22 22:34:36 This potentially desynchronizes stdout & stderr.
dnj 2014/07/22 23:16:20 You're completely correct. However, AFAIK any assu
59 stderr=subprocess.PIPE,
60 )
61 stderr_tee = TeeThread(proc.stderr, sys.stderr, 'stderr')
62
63 # Start our process. Collect/tee 'stdout' and 'stderr'.
64 stderr_tee.start()
65 try:
66 proc.wait()
67 except KeyboardInterrupt:
68 proc.kill()
69 raise
70 finally:
71 stderr_tee.join()
72 return proc.returncode, None, stderr_tee.data
Peter Mayo 2014/07/22 22:34:36 returning stdout as None because we left it on sys
dnj 2014/07/22 23:16:20 Just leaving room in case we need to filter on STD
73
74 def __call__(self, *args):
75 delay = self.delay
76
77 returncode = 0
78 for i in xrange(self.retry_count):
79 self.logger.info("Executing subprocess (%d/%d) with arguments: %s",
80 (i+1), self.retry_count, args)
81 returncode, stdout, stderr = self.execute(*args)
82
83 self.logger.debug("Process terminated with return code: %d", returncode)
84 if returncode == 0:
85 break
86
87 if not self.shouldRetry(stderr):
88 self.logger.error("Process failure was not known to be transient; "
89 "terminating with return code %d", returncode)
90 break
91
92 # Delay (if requested)
Peter Mayo 2014/07/22 22:34:36 - Doesn't make sense to delay after last (failed)
dnj 2014/07/22 23:16:20 Done.
93 if delay:
94 self.logger.debug("Delaying for %s until next retry", delay)
95 time.sleep(delay.total_seconds())
96 if self.delay_exponential:
97 delay *= 2
98 return returncode
99
100
101 def main(args):
102 parser = optparse.OptionParser()
103 parser.disable_interspersed_args()
104 parser.add_option('-v', '--verbose',
105 action='count', default=0,
106 help="Increase verbosity; can be specified multiple times")
107 parser.add_option('-c', '--retry-count', metavar='COUNT',
108 type=int, default=GitRetry.DEFAULT_RETRY_COUNT,
109 help="Number of times to retry (default=%default)")
110 parser.add_option('-d', '--delay', metavar='SECONDS',
111 type=int, default=0,
112 help="Specifies the amount of time (in milliseconds) to "
113 "wait between successive retries.")
114 parser.add_option('-e', '--delay-exponential',
115 action='store_true',
116 help="If specified, the amount of delay between successive "
117 "retries will double with each retry.")
118
119 opts, args = parser.parse_args(args)
120
121 # Configure logging verbosity
122 if opts.verbose == 0:
123 logging.getLogger().setLevel(logging.WARNING)
124 elif opts.verbose == 1:
125 logging.getLogger().setLevel(logging.INFO)
126 else:
127 logging.getLogger().setLevel(logging.DEBUG)
128
129 # Convert 'delay' to timedelta
130 delay = datetime.timedelta(seconds=opts.delay) if opts.delay else None
131
132 # Execute retries
133 retry = GitRetry(
134 retry_count=opts.retry_count,
135 delay=delay,
136 delay_exponential=opts.delay_exponential,
137 )
138 return retry(*args)
139
140
141 if __name__ == '__main__':
142 logging.basicConfig()
143 logging.getLogger().setLevel(logging.WARNING)
144 sys.exit(main(sys.argv[2:]))
OLDNEW
« no previous file with comments | « git_common.py ('k') | man/html/git-retry.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698