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 210 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
221 try: | 221 try: |
222 return f(*args, **kwargs) | 222 return f(*args, **kwargs) |
223 except Exception as e: | 223 except Exception as e: |
224 if (i+1) >= self.retries or not self.condition(e): | 224 if (i+1) >= self.retries or not self.condition(e): |
225 raise | 225 raise |
226 logging.exception('Exception encountered, retrying in %s', | 226 logging.exception('Exception encountered, retrying in %s', |
227 retry_delay) | 227 retry_delay) |
228 time.sleep(retry_delay.total_seconds()) | 228 time.sleep(retry_delay.total_seconds()) |
229 retry_delay *= 2 | 229 retry_delay *= 2 |
230 return wrapper | 230 return wrapper |
| 231 |
| 232 |
| 233 class MultiException(Exception): |
| 234 """An exception that aggregates multiple exceptions and summarizes them.""" |
| 235 |
| 236 def __init__(self): |
| 237 self._inner = [] |
| 238 |
| 239 def __nonzero__(self): |
| 240 return bool(self._inner) |
| 241 |
| 242 def __len__(self): |
| 243 return len(self._inner) |
| 244 |
| 245 def __getitem__(self, key): |
| 246 return self._inner[key] |
| 247 |
| 248 def __iter__(self): |
| 249 return iter(self._inner) |
| 250 |
| 251 def __str__(self): |
| 252 if len(self._inner) == 0: |
| 253 text = 'No exceptions' |
| 254 elif len(self._inner) == 1: |
| 255 text = str(self._inner[0]) |
| 256 else: |
| 257 text = str(self._inner[0]) + ', and %d more...' % (len(self._inner)-1) |
| 258 return '%s(%s)' % (type(self).__name__, text) |
| 259 |
| 260 def append(self, exc): |
| 261 assert isinstance(exc, BaseException) |
| 262 self._inner.append(exc) |
| 263 |
| 264 def raise_if_any(self): |
| 265 if self._inner: |
| 266 raise self |
| 267 |
| 268 @contextlib.contextmanager |
| 269 def catch(self, *exc_types): |
| 270 """ContextManager that catches any exception raised during its execution |
| 271 and adds them to the MultiException. |
| 272 |
| 273 Args: |
| 274 exc_types (list): A list of exception classes to catch. If empty, |
| 275 Exception will be used. |
| 276 """ |
| 277 exc_types = exc_types or (Exception,) |
| 278 try: |
| 279 yield |
| 280 except exc_types as e: |
| 281 self.append(e) |
| 282 |
| 283 |
| 284 @contextlib.contextmanager |
| 285 def defer_exceptions_for(it, fn, *exc_types): |
| 286 """Executes "fn" for each element in "it". Any exceptions thrown by "fn" will |
| 287 be deferred until the end of "it", then raised as a single MultiException. |
| 288 |
| 289 Args: |
| 290 it (iterable): An iterable to traverse. |
| 291 fn (callable): A function to call for each element in "it". |
| 292 exc_types (list): An optional list of specific exception types to handle. |
| 293 If empty, Exception will be used. |
| 294 """ |
| 295 mexc = MultiException() |
| 296 for e in it: |
| 297 with mexc.catch(*exc_types): |
| 298 fn(e) |
| 299 mexc.raise_if_any() |
OLD | NEW |