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

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/ comments. 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 logging
7 import optparse
8 import subprocess
9 import sys
10 import threading
11 import time
12
13 from git_common import GIT_EXE, GIT_TRANSIENT_ERRORS_RE
14
15
16 class TeeThread(threading.Thread):
17
18 def __init__(self, fd, out_fd, name):
19 super(TeeThread, self).__init__(name='git-retry.tee.%s' % (name,))
20 self.data = None
21 self.fd = fd
22 self.out_fd = out_fd
23
24 def run(self):
25 chunks = []
26 for line in self.fd:
27 chunks.append(line)
28 self.out_fd.write(line)
29 self.data = ''.join(chunks)
30
31
32 class GitRetry(object):
33
34 logger = logging.getLogger('git-retry')
35 DEFAULT_DELAY_SECS = 3.0
36 DEFAULT_RETRY_COUNT = 5
37
38 def __init__(self, retry_count=None, accepted_return_codes=None, delay=None,
39 delay_factor=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
43 self.delay_factor = delay_factor
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 self.logger.info("Encountered known transient error: [%s]",
51 stderr[m.start(): m.end()])
52 return True
53
54 @staticmethod
55 def execute(*args):
56 args = (GIT_EXE,) + args
57 proc = subprocess.Popen(
58 args,
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
73
74 def computeDelay(self, iteration):
75 """Returns: the delay (in seconds) for a given iteration
76
77 The first iteration has a delay of '0'.
78
79 Args:
80 iteration: (int) The iteration index (starting with zero as the first
81 iteration)
82 """
83 if (not self.delay) or (iteration == 0):
84 return 0
85 if self.delay_factor <= 0:
86 # Linear delay
87 return iteration * self.delay
Peter Mayo 2014/07/22 23:39:55 won't this be negative in the usual case?
dnj 2014/07/23 00:04:52 The negative was more of a catch-all; the usual ca
88 # Exponential delay
89 return (self.delay_factor ** (iteration - 1)) * self.delay
Peter Mayo 2014/07/22 23:39:55 Consider dividing and using (count - iteration) in
dnj 2014/07/23 00:04:52 I don't want to make this 'factor' number too comp
90
91 def __call__(self, *args):
92 returncode = 0
93 for i in xrange(self.retry_count):
94 # If the previous run failed and a delay is configured, delay before the
95 # next run.
96 delay = self.computeDelay(i)
97 if delay > 0:
98 self.logger.info("Delaying for [%s second(s)] until next retry", delay)
99 time.sleep(delay)
100
101 self.logger.debug("Executing subprocess (%d/%d) with arguments: %s",
102 (i+1), self.retry_count, args)
103 returncode, _, stderr = self.execute(*args)
104
105 self.logger.debug("Process terminated with return code: %d", returncode)
106 if returncode == 0:
107 break
108
109 if not self.shouldRetry(stderr):
110 self.logger.error("Process failure was not known to be transient; "
111 "terminating with return code %d", returncode)
112 break
113 return returncode
114
115
116 def main(args):
117 parser = optparse.OptionParser()
118 parser.disable_interspersed_args()
119 parser.add_option('-v', '--verbose',
120 action='count', default=0,
121 help="Increase verbosity; can be specified multiple times")
122 parser.add_option('-c', '--retry-count', metavar='COUNT',
123 type=int, default=GitRetry.DEFAULT_RETRY_COUNT,
124 help="Number of times to retry (default=%default)")
125 parser.add_option('-d', '--delay', metavar='SECONDS',
126 type=float, default=GitRetry.DEFAULT_DELAY_SECS,
127 help="Specifies the amount of time (in seconds) to wait "
128 "between successive retries (default=%default). This "
129 "can be zero.")
130 parser.add_option('-D', '--delay-factor', metavar='FACTOR',
131 type=int, default=2,
132 help="The exponential factor to apply to delays in between "
133 "successive failures (default=%default). If this is "
134 "zero, delays will increase linearly. Set this to "
135 "one to have a constant (non-increasing) delay.")
136
137 opts, args = parser.parse_args(args)
138
139 # Configure logging verbosity
140 if opts.verbose == 0:
141 logging.getLogger().setLevel(logging.WARNING)
142 elif opts.verbose == 1:
143 logging.getLogger().setLevel(logging.INFO)
144 else:
145 logging.getLogger().setLevel(logging.DEBUG)
146
147 # Execute retries
148 retry = GitRetry(
149 retry_count=opts.retry_count,
150 delay=opts.delay,
151 delay_factor=opts.delay_factor,
152 )
153 return retry(*args)
154
155
156 if __name__ == '__main__':
157 logging.basicConfig()
158 logging.getLogger().setLevel(logging.WARNING)
159 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