|
19 | 19 |
|
20 | 20 | from google.api_core import retry as core_retry |
21 | 21 | from google.api_core import exceptions as core_exceptions |
| 22 | +from google.cloud.ndb import exceptions |
22 | 23 | from google.cloud.ndb import tasklets |
23 | 24 |
|
24 | 25 | _DEFAULT_INITIAL_DELAY = 1.0 # seconds |
@@ -59,24 +60,47 @@ def retry_async(callback, retries=_DEFAULT_RETRIES): |
59 | 60 | @tasklets.tasklet |
60 | 61 | @wraps_safely(callback) |
61 | 62 | def retry_wrapper(*args, **kwargs): |
| 63 | + from google.cloud.ndb import context as context_module |
| 64 | + |
62 | 65 | sleep_generator = core_retry.exponential_sleep_generator( |
63 | 66 | _DEFAULT_INITIAL_DELAY, |
64 | 67 | _DEFAULT_MAXIMUM_DELAY, |
65 | 68 | _DEFAULT_DELAY_MULTIPLIER, |
66 | 69 | ) |
67 | 70 |
|
68 | 71 | for sleep_time in itertools.islice(sleep_generator, retries + 1): |
| 72 | + context = context_module.get_context() |
| 73 | + if not context.in_retry(): |
| 74 | + # We need to be able to identify if we are inside a nested |
| 75 | + # retry. Here, we set the retry state in the context. This is |
| 76 | + # used for deciding if an exception should be raised |
| 77 | + # immediately or passed up to the outer retry block. |
| 78 | + context.set_retry_state(repr(callback)) |
69 | 79 | try: |
70 | 80 | result = callback(*args, **kwargs) |
71 | 81 | if isinstance(result, tasklets.Future): |
72 | 82 | result = yield result |
| 83 | + except exceptions.NestedRetryException as e: |
| 84 | + error = e |
73 | 85 | except Exception as e: |
74 | 86 | # `e` is removed from locals at end of block |
75 | 87 | error = e # See: https://goo.gl/5J8BMK |
76 | 88 | if not is_transient_error(error): |
77 | | - raise error |
| 89 | + # If we are in an inner retry block, use special nested |
| 90 | + # retry exception to bubble up to outer retry. Else, raise |
| 91 | + # actual exception. |
| 92 | + if context.get_retry_state() != repr(callback): |
| 93 | + message = getattr(error, "message", str(error)) |
| 94 | + raise exceptions.NestedRetryException(message) |
| 95 | + else: |
| 96 | + raise error |
78 | 97 | else: |
79 | 98 | raise tasklets.Return(result) |
| 99 | + finally: |
| 100 | + # No matter what, if we are exiting the top level retry, |
| 101 | + # clear the retry state in the context. |
| 102 | + if context.get_retry_state() == repr(callback): # pragma: NO BRANCH |
| 103 | + context.clear_retry_state() |
80 | 104 |
|
81 | 105 | yield tasklets.sleep(sleep_time) |
82 | 106 |
|
|
0 commit comments