Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / lib / retry_util.py
1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Basic infrastructure for implementing retries."""
6
7 from __future__ import print_function
8
9 import logging
10 import sys
11 import time
12
13 from chromite.lib import cros_build_lib
14
15
16 def GenericRetry(handler, max_retry, functor, *args, **kwargs):
17   """Generic retry loop w/ optional break out depending on exceptions.
18
19   To retry based on the return value of |functor| see the timeout_util module.
20
21   Args:
22     handler: A functor invoked w/ the exception instance that
23       functor(*args, **kwargs) threw.  If it returns True, then a
24       retry is attempted.  If False, the exception is re-raised.
25     max_retry: A positive integer representing how many times to retry
26       the command before giving up.  Worst case, the command is invoked
27       (max_retry + 1) times before failing.
28     functor: A callable to pass args and kwargs to.
29     args: Positional args passed to functor.
30     kwargs: Optional args passed to functor.
31     sleep: Optional keyword.  Multiplier for how long to sleep between
32       retries; will delay (1*sleep) the first time, then (2*sleep),
33       continuing via attempt * sleep.
34
35   Returns:
36     Whatever functor(*args, **kwargs) returns.
37
38   Raises:
39     Exception: Whatever exceptions functor(*args, **kwargs) throws and
40       isn't suppressed is raised.  Note that the first exception encountered
41       is what's thrown.
42   """
43
44   sleep = kwargs.pop('sleep', 0)
45   if max_retry < 0:
46     raise ValueError('max_retry needs to be zero or more: %s' % max_retry)
47
48   exc_info = None
49   for attempt in xrange(max_retry + 1):
50     if attempt and sleep:
51       time.sleep(sleep * attempt)
52     try:
53       return functor(*args, **kwargs)
54     except Exception as e:
55       # Note we're not snagging BaseException, so MemoryError/KeyboardInterrupt
56       # and friends don't enter this except block.
57       if not handler(e):
58         raise
59       # We intentionally ignore any failures in later attempts since we'll
60       # throw the original failure if all retries fail.
61       if exc_info is None:
62         exc_info = sys.exc_info()
63
64   raise exc_info[0], exc_info[1], exc_info[2]
65
66
67 def RetryException(exc_retry, max_retry, functor, *args, **kwargs):
68   """Convience wrapper for RetryInvocation based on exceptions.
69
70   Args:
71     exc_retry: A class (or tuple of classes).  If the raised exception
72       is the given class(es), a retry will be attempted.  Otherwise,
73       the exception is raised.
74     max_retry: See GenericRetry.
75     functor: See GenericRetry.
76     *args: See GenericRetry.
77     **kwargs: See GenericRetry.
78   """
79   if not isinstance(exc_retry, (tuple, type)):
80     raise TypeError('exc_retry should be an exception (or tuple), not %r' %
81                     exc_retry)
82   #pylint: disable=E0102
83   def exc_retry(exc, values=exc_retry):
84     return isinstance(exc, values)
85   return GenericRetry(exc_retry, max_retry, functor, *args, **kwargs)
86
87
88 def RetryCommand(functor, max_retry, *args, **kwargs):
89   """Wrapper for RunCommand that will retry a command
90
91   Args:
92     functor: RunCommand function to run; retries will only occur on
93       RunCommandError exceptions being thrown.
94     max_retry: A positive integer representing how many times to retry
95       the command before giving up.  Worst case, the command is invoked
96       (max_retry + 1) times before failing.
97     sleep: Optional keyword.  Multiplier for how long to sleep between
98       retries; will delay (1*sleep) the first time, then (2*sleep),
99       continuing via attempt * sleep.
100     retry_on: If provided, we will retry on any exit codes in the given list.
101       Note: A process will exit with a negative exit code if it is killed by a
102       signal. By default, we retry on all non-negative exit codes.
103     args: Positional args passed to RunCommand; see RunCommand for specifics.
104     kwargs: Optional args passed to RunCommand; see RunCommand for specifics.
105
106   Returns:
107     A CommandResult object.
108
109   Raises:
110     Exception:  Raises RunCommandError on error with optional error_message.
111   """
112   values = kwargs.pop('retry_on', None)
113   def ShouldRetry(exc):
114     """Return whether we should retry on a given exception."""
115     if not ShouldRetryCommandCommon(exc):
116       return False
117     if values is None and exc.result.returncode < 0:
118       logging.info('Child process received signal %d; not retrying.',
119                    -exc.result.returncode)
120       return False
121     return values is None or exc.result.returncode in values
122   return GenericRetry(ShouldRetry, max_retry, functor, *args, **kwargs)
123
124
125 def ShouldRetryCommandCommon(exc):
126   """Returns whether any RunCommand should retry on a given exception."""
127   if not isinstance(exc, cros_build_lib.RunCommandError):
128     return False
129   if exc.result.returncode is None:
130     logging.info('Child process failed to launch; not retrying.')
131     return False
132   return True
133
134
135 def RunCommandWithRetries(max_retry, *args, **kwargs):
136   """Wrapper for RunCommand that will retry a command
137
138   Args:
139     max_retry: See RetryCommand and RunCommand.
140     *args: See RetryCommand and RunCommand.
141     **kwargs: See RetryCommand and RunCommand.
142
143   Returns:
144     A CommandResult object.
145
146   Raises:
147     Exception:  Raises RunCommandError on error with optional error_message.
148   """
149   return RetryCommand(cros_build_lib.RunCommand, max_retry, *args, **kwargs)
150
151
152 def RunCurl(args, **kwargs):
153   """Runs curl and wraps around all necessary hacks."""
154   cmd = ['curl']
155   cmd.extend(args)
156
157   # These values were discerned via scraping the curl manpage; they're all
158   # retry related (dns failed, timeout occurred, etc, see  the manpage for
159   # exact specifics of each).
160   # Note we allow 22 to deal w/ 500's- they're thrown by google storage
161   # occasionally.
162   # Note we allow 35 to deal w/ Unknown SSL Protocol error, thrown by
163   # google storage occasionally.
164   # Finally, we do not use curl's --retry option since it generally doesn't
165   # actually retry anything; code 18 for example, it will not retry on.
166   retriable_exits = frozenset([5, 6, 7, 15, 18, 22, 26, 28, 35, 52, 56])
167   try:
168     return RunCommandWithRetries(5, cmd, sleep=3, retry_on=retriable_exits,
169                                  **kwargs)
170   except cros_build_lib.RunCommandError as e:
171     code = e.result.returncode
172     if code in (51, 58, 60):
173       # These are the return codes of failing certs as per 'man curl'.
174       msg = 'Download failed with certificate error? Try "sudo c_rehash".'
175       cros_build_lib.Die(msg)
176     else:
177       try:
178         return RunCommandWithRetries(5, cmd, sleep=60, retry_on=retriable_exits,
179                                      **kwargs)
180       except cros_build_lib.RunCommandError as e:
181         cros_build_lib.Die("Curl failed w/ exit code %i", code)