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

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: More updates. 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, delay=None, delay_factor=None):
39 self.retry_count = retry_count or self.DEFAULT_RETRY_COUNT
40 self.delay = max(delay, 0) if delay else 0
41 self.delay_factor = max(delay_factor, 0) if delay_factor else 0
42
43 def shouldRetry(self, stderr):
44 m = GIT_TRANSIENT_ERRORS_RE.search(stderr)
45 if not m:
46 return False
47 self.logger.info("Encountered known transient error: [%s]",
48 stderr[m.start(): m.end()])
49 return True
50
51 @staticmethod
52 def execute(*args):
53 args = (GIT_EXE,) + args
54 proc = subprocess.Popen(
55 args,
56 stderr=subprocess.PIPE,
57 )
58 stderr_tee = TeeThread(proc.stderr, sys.stderr, 'stderr')
59
60 # Start our process. Collect/tee 'stdout' and 'stderr'.
61 stderr_tee.start()
62 try:
63 proc.wait()
64 except KeyboardInterrupt:
65 proc.kill()
66 raise
67 finally:
68 stderr_tee.join()
69 return proc.returncode, None, stderr_tee.data
70
71 def computeDelay(self, iteration):
72 """Returns: the delay (in seconds) for a given iteration
73
74 The first iteration has a delay of '0'.
75
76 Args:
77 iteration: (int) The iteration index (starting with zero as the first
78 iteration)
79 """
80 if (not self.delay) or (iteration == 0):
81 return 0
82 if self.delay_factor == 0:
83 # Linear delay
84 return iteration * self.delay
85 # Exponential delay
86 return (self.delay_factor ** (iteration - 1)) * self.delay
87
88 def __call__(self, *args):
89 returncode = 0
90 for i in xrange(self.retry_count):
91 # If the previous run failed and a delay is configured, delay before the
92 # next run.
93 delay = self.computeDelay(i)
94 if delay > 0:
95 self.logger.info("Delaying for [%s second(s)] until next retry", delay)
96 time.sleep(delay)
97
98 self.logger.debug("Executing subprocess (%d/%d) with arguments: %s",
99 (i+1), self.retry_count, args)
100 returncode, _, stderr = self.execute(*args)
101
102 self.logger.debug("Process terminated with return code: %d", returncode)
103 if returncode == 0:
104 break
105
106 if not self.shouldRetry(stderr):
107 self.logger.error("Process failure was not known to be transient; "
108 "terminating with return code %d", returncode)
109 break
110 return returncode
111
112
113 def main(args):
114 parser = optparse.OptionParser()
115 parser.disable_interspersed_args()
116 parser.add_option('-v', '--verbose',
117 action='count', default=0,
118 help="Increase verbosity; can be specified multiple times")
119 parser.add_option('-c', '--retry-count', metavar='COUNT',
120 type=int, default=GitRetry.DEFAULT_RETRY_COUNT,
121 help="Number of times to retry (default=%default)")
122 parser.add_option('-d', '--delay', metavar='SECONDS',
123 type=float, default=GitRetry.DEFAULT_DELAY_SECS,
124 help="Specifies the amount of time (in seconds) to wait "
125 "between successive retries (default=%default). This "
126 "can be zero.")
127 parser.add_option('-D', '--delay-factor', metavar='FACTOR',
128 type=int, default=2,
129 help="The exponential factor to apply to delays in between "
130 "successive failures (default=%default). If this is "
131 "zero, delays will increase linearly. Set this to "
132 "one to have a constant (non-increasing) delay.")
133
134 opts, args = parser.parse_args(args)
135
136 # Configure logging verbosity
137 if opts.verbose == 0:
138 logging.getLogger().setLevel(logging.WARNING)
139 elif opts.verbose == 1:
140 logging.getLogger().setLevel(logging.INFO)
141 else:
142 logging.getLogger().setLevel(logging.DEBUG)
143
144 # Execute retries
145 retry = GitRetry(
146 retry_count=opts.retry_count,
147 delay=opts.delay,
148 delay_factor=opts.delay_factor,
149 )
150 return retry(*args)
151
152
153 if __name__ == '__main__':
154 logging.basicConfig()
155 logging.getLogger().setLevel(logging.WARNING)
156 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