OLD | NEW |
1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
4 | 4 |
5 import contextlib | 5 import contextlib |
6 import datetime | 6 import datetime |
7 import functools | 7 import functools |
8 import logging | 8 import logging |
9 import os | 9 import os |
10 import sys | 10 import sys |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
133 for root, _dirs, files in os.walk(path): | 133 for root, _dirs, files in os.walk(path): |
134 for file_name in (f for f in files if predicate(f)): | 134 for file_name in (f for f in files if predicate(f)): |
135 file_path = os.path.join(root, file_name) | 135 file_path = os.path.join(root, file_name) |
136 yield file_path | 136 yield file_path |
137 | 137 |
138 | 138 |
139 BUG_LINK = ( | 139 BUG_LINK = ( |
140 'https://code.google.com/p/chromium/issues/entry?%s' % urllib.urlencode({ | 140 'https://code.google.com/p/chromium/issues/entry?%s' % urllib.urlencode({ |
141 'summary': 'Recipe engine bug: unexpected failure', | 141 'summary': 'Recipe engine bug: unexpected failure', |
142 'comment': 'Link to the failing build and paste the exception here', | 142 'comment': 'Link to the failing build and paste the exception here', |
143 'labels': 'Infra,Infra-Area-Recipes,Pri-1,Restrict-View-Google,Infra-Tro
opers', | 143 'labels': 'Infra,Infra-Area-Recipes,Pri-1,Restrict-View-Google,' |
| 144 'Infra-Troopers', |
144 'cc': 'martiniss@chromium.org,iannucci@chromium.org', | 145 'cc': 'martiniss@chromium.org,iannucci@chromium.org', |
145 })) | 146 })) |
146 | 147 |
147 | 148 |
148 @contextlib.contextmanager | 149 @contextlib.contextmanager |
149 def raises(exc_cls, stream_engine=None): | 150 def raises(exc_cls, stream_engine=None): |
150 """If the body raises an exception not in exc_cls, print and abort the engine. | 151 """If the body raises an exception not in exc_cls, print and abort the engine. |
151 | 152 |
152 This is so that we have something to go on when a function goes wrong, yet the | 153 This is so that we have something to go on when a function goes wrong, yet the |
153 exception is covered up by something else (e.g. an error in a finally block). | 154 exception is covered up by something else (e.g. an error in a finally block). |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
221 try: | 222 try: |
222 return f(*args, **kwargs) | 223 return f(*args, **kwargs) |
223 except Exception as e: | 224 except Exception as e: |
224 if (i+1) >= self.retries or not self.condition(e): | 225 if (i+1) >= self.retries or not self.condition(e): |
225 raise | 226 raise |
226 logging.exception('Exception encountered, retrying in %s', | 227 logging.exception('Exception encountered, retrying in %s', |
227 retry_delay) | 228 retry_delay) |
228 time.sleep(retry_delay.total_seconds()) | 229 time.sleep(retry_delay.total_seconds()) |
229 retry_delay *= 2 | 230 retry_delay *= 2 |
230 return wrapper | 231 return wrapper |
| 232 |
| 233 |
| 234 class MultiException(Exception): |
| 235 """An exception that aggregates multiple exceptions and summarizes them.""" |
| 236 |
| 237 class Builder(object): |
| 238 """Iteratively constructs a MultiException.""" |
| 239 |
| 240 def __init__(self): |
| 241 self._exceptions = [] |
| 242 |
| 243 def append(self, exc): |
| 244 if exc is not None: |
| 245 self._exceptions.append(exc) |
| 246 |
| 247 def get(self): |
| 248 """Returns (MultiException or None): The constructed MultiException. |
| 249 |
| 250 If no exceptions have been appended, None will be returned. |
| 251 """ |
| 252 return MultiException(*self._exceptions) if self._exceptions else (None) |
| 253 |
| 254 def raise_if_any(self): |
| 255 mexc = self.get() |
| 256 if mexc is not None: |
| 257 raise mexc |
| 258 |
| 259 @contextlib.contextmanager |
| 260 def catch(self, *exc_types): |
| 261 """ContextManager that catches any exception raised during its execution |
| 262 and adds them to the MultiException. |
| 263 |
| 264 Args: |
| 265 exc_types (list): A list of exception classes to catch. If empty, |
| 266 Exception will be used. |
| 267 """ |
| 268 exc_types = exc_types or (Exception,) |
| 269 try: |
| 270 yield |
| 271 except exc_types as exc: |
| 272 self.append(exc) |
| 273 |
| 274 |
| 275 def __init__(self, *base): |
| 276 super(MultiException, self).__init__() |
| 277 |
| 278 # Determine base Exception text. |
| 279 if len(base) == 0: |
| 280 self.message = 'No exceptions' |
| 281 elif len(base) == 1: |
| 282 self.message = str(base[0]) |
| 283 else: |
| 284 self.message = str(base[0]) + ', and %d more...' % (len(base)-1) |
| 285 self._inner = base |
| 286 |
| 287 def __nonzero__(self): |
| 288 return bool(self._inner) |
| 289 |
| 290 def __len__(self): |
| 291 return len(self._inner) |
| 292 |
| 293 def __getitem__(self, key): |
| 294 return self._inner[key] |
| 295 |
| 296 def __iter__(self): |
| 297 return iter(self._inner) |
| 298 |
| 299 def __str__(self): |
| 300 return '%s(%s)' % (type(self).__name__, self.message) |
| 301 |
| 302 |
| 303 @contextlib.contextmanager |
| 304 def map_defer_exceptions(fn, it, *exc_types): |
| 305 """Executes "fn" for each element in "it". Any exceptions thrown by "fn" will |
| 306 be deferred until the end of "it", then raised as a single MultiException. |
| 307 |
| 308 Args: |
| 309 fn (callable): A function to call for each element in "it". |
| 310 it (iterable): An iterable to traverse. |
| 311 exc_types (list): An optional list of specific exception types to defer. |
| 312 If empty, Exception will be used. Any Exceptions not referenced by this |
| 313 list will skip deferring and be immediately raised. |
| 314 """ |
| 315 mexc_builder = MultiException.Builder() |
| 316 for e in it: |
| 317 with mexc_builder.catch(*exc_types): |
| 318 fn(e) |
| 319 mexc_builder.raise_if_any() |
OLD | NEW |