|
5 | 5 | import threading |
6 | 6 | import weakref |
7 | 7 | import warnings |
| 8 | +from kombu.utils.functional import retry_over_time |
8 | 9 | from celery.exceptions import CDeprecationWarning |
9 | 10 | from celery.five import python_2_unicode_compatible, range, text_t |
10 | 11 | from celery.local import PromiseProxy, Proxy |
11 | 12 | from celery.utils.functional import fun_accepts_kwargs |
| 13 | +from celery.utils.time import humanize_seconds |
12 | 14 | from celery.utils.log import get_logger |
13 | 15 | try: |
14 | 16 | from weakref import WeakMethod |
@@ -36,6 +38,10 @@ def _make_id(target): # pragma: no cover |
36 | 38 |
|
37 | 39 | NO_RECEIVERS = object() |
38 | 40 |
|
| 41 | +RECEIVER_RETRY_ERROR = """\ |
| 42 | +Could not process signal receiver %(receiver)s. Retrying %(when)s...\ |
| 43 | +""" |
| 44 | + |
39 | 45 |
|
40 | 46 | @python_2_unicode_compatible |
41 | 47 | class Signal(object): # pragma: no cover |
@@ -103,12 +109,49 @@ def connect(self, *args, **kwargs): |
103 | 109 | dispatch_uid (Hashable): An identifier used to uniquely identify a |
104 | 110 | particular instance of a receiver. This will usually be a |
105 | 111 | string, though it may be anything hashable. |
| 112 | +
|
| 113 | + retry (bool): If the signal receiver raises an exception |
| 114 | + (e.g. ConnectionError), the receiver will be retried until it |
| 115 | + runs successfully. A strong ref to the receiver will be stored |
| 116 | + and the `weak` option will be ignored. |
106 | 117 | """ |
107 | | - def _handle_options(sender=None, weak=True, dispatch_uid=None): |
| 118 | + def _handle_options(sender=None, weak=True, dispatch_uid=None, |
| 119 | + retry=False): |
108 | 120 |
|
109 | 121 | def _connect_signal(fun): |
110 | | - self._connect_signal(fun, sender, weak, dispatch_uid) |
| 122 | + |
| 123 | + options = {'dispatch_uid': dispatch_uid, |
| 124 | + 'weak': weak} |
| 125 | + |
| 126 | + def _retry_receiver(retry_fun): |
| 127 | + |
| 128 | + def _try_receiver_over_time(*args, **kwargs): |
| 129 | + def on_error(exc, intervals, retries): |
| 130 | + interval = next(intervals) |
| 131 | + err_msg = RECEIVER_RETRY_ERROR % \ |
| 132 | + {'receiver': retry_fun, |
| 133 | + 'when': humanize_seconds(interval, 'in', ' ')} |
| 134 | + logger.error(err_msg) |
| 135 | + return interval |
| 136 | + |
| 137 | + return retry_over_time(retry_fun, Exception, args, |
| 138 | + kwargs, on_error) |
| 139 | + |
| 140 | + return _try_receiver_over_time |
| 141 | + |
| 142 | + if retry: |
| 143 | + options['weak'] = False |
| 144 | + if not dispatch_uid: |
| 145 | + # if there's no dispatch_uid then we need to set the |
| 146 | + # dispatch uid to the original func id so we can look |
| 147 | + # it up later with the original func id |
| 148 | + options['dispatch_uid'] = _make_id(fun) |
| 149 | + fun = _retry_receiver(fun) |
| 150 | + |
| 151 | + self._connect_signal(fun, sender, options['weak'], |
| 152 | + options['dispatch_uid']) |
111 | 153 | return fun |
| 154 | + |
112 | 155 | return _connect_signal |
113 | 156 |
|
114 | 157 | if args and callable(args[0]): |
@@ -158,6 +201,7 @@ def _connect_signal(self, receiver, sender, weak, dispatch_uid): |
158 | 201 | else: |
159 | 202 | self.receivers.append((lookup_key, receiver)) |
160 | 203 | self.sender_receivers_cache.clear() |
| 204 | + |
161 | 205 | return receiver |
162 | 206 |
|
163 | 207 | def disconnect(self, receiver=None, sender=None, weak=None, |
|
0 commit comments