|
@@ -13,23 +13,27 @@
|
|
"""
|
|
"""
|
|
from __future__ import absolute_import
|
|
from __future__ import absolute_import
|
|
|
|
|
|
-import time
|
|
|
|
|
|
+import socket
|
|
import sys
|
|
import sys
|
|
|
|
+import time
|
|
|
|
|
|
|
|
+from collections import deque
|
|
from datetime import timedelta
|
|
from datetime import timedelta
|
|
|
|
+from weakref import WeakKeyDictionary
|
|
|
|
|
|
from billiard.einfo import ExceptionInfo
|
|
from billiard.einfo import ExceptionInfo
|
|
from kombu.serialization import (
|
|
from kombu.serialization import (
|
|
dumps, loads, prepare_accept_content,
|
|
dumps, loads, prepare_accept_content,
|
|
registry as serializer_registry,
|
|
registry as serializer_registry,
|
|
)
|
|
)
|
|
|
|
+from kombu.syn import detect_environment
|
|
from kombu.utils.encoding import bytes_to_str, ensure_bytes, from_utf8
|
|
from kombu.utils.encoding import bytes_to_str, ensure_bytes, from_utf8
|
|
|
|
|
|
from celery import states
|
|
from celery import states
|
|
from celery import current_app, group, maybe_signature
|
|
from celery import current_app, group, maybe_signature
|
|
from celery.app import current_task
|
|
from celery.app import current_task
|
|
from celery.exceptions import ChordError, TimeoutError, TaskRevokedError
|
|
from celery.exceptions import ChordError, TimeoutError, TaskRevokedError
|
|
-from celery.five import items
|
|
|
|
|
|
+from celery.five import items, monotonic
|
|
from celery.result import (
|
|
from celery.result import (
|
|
GroupResult, ResultBase, allow_join_result, result_from_tuple,
|
|
GroupResult, ResultBase, allow_join_result, result_from_tuple,
|
|
)
|
|
)
|
|
@@ -61,7 +65,7 @@ class _nulldict(dict):
|
|
__setitem__ = update = setdefault = ignore
|
|
__setitem__ = update = setdefault = ignore
|
|
|
|
|
|
|
|
|
|
-class BaseBackend(object):
|
|
|
|
|
|
+class Backend(object):
|
|
READY_STATES = states.READY_STATES
|
|
READY_STATES = states.READY_STATES
|
|
UNREADY_STATES = states.UNREADY_STATES
|
|
UNREADY_STATES = states.UNREADY_STATES
|
|
EXCEPTION_STATES = states.EXCEPTION_STATES
|
|
EXCEPTION_STATES = states.EXCEPTION_STATES
|
|
@@ -222,46 +226,6 @@ class BaseBackend(object):
|
|
content_encoding=self.content_encoding,
|
|
content_encoding=self.content_encoding,
|
|
accept=self.accept)
|
|
accept=self.accept)
|
|
|
|
|
|
- def wait_for_pending(self, result, timeout=None, interval=0.5,
|
|
|
|
- no_ack=True, on_interval=None, callback=None,
|
|
|
|
- propagate=True):
|
|
|
|
- meta = self.wait_for(
|
|
|
|
- result.id, timeout=timeout,
|
|
|
|
- interval=interval,
|
|
|
|
- on_interval=on_interval,
|
|
|
|
- no_ack=no_ack,
|
|
|
|
- )
|
|
|
|
- if meta:
|
|
|
|
- result._maybe_set_cache(meta)
|
|
|
|
- return result.maybe_throw(propagate=propagate, callback=callback)
|
|
|
|
-
|
|
|
|
- def wait_for(self, task_id,
|
|
|
|
- timeout=None, interval=0.5, no_ack=True, on_interval=None):
|
|
|
|
- """Wait for task and return its result.
|
|
|
|
-
|
|
|
|
- If the task raises an exception, this exception
|
|
|
|
- will be re-raised by :func:`wait_for`.
|
|
|
|
-
|
|
|
|
- If `timeout` is not :const:`None`, this raises the
|
|
|
|
- :class:`celery.exceptions.TimeoutError` exception if the operation
|
|
|
|
- takes longer than `timeout` seconds.
|
|
|
|
-
|
|
|
|
- """
|
|
|
|
-
|
|
|
|
- time_elapsed = 0.0
|
|
|
|
-
|
|
|
|
- while 1:
|
|
|
|
- meta = self.get_task_meta(task_id)
|
|
|
|
- if meta['status'] in states.READY_STATES:
|
|
|
|
- return meta
|
|
|
|
- if on_interval:
|
|
|
|
- on_interval()
|
|
|
|
- # avoid hammering the CPU checking status.
|
|
|
|
- time.sleep(interval)
|
|
|
|
- time_elapsed += interval
|
|
|
|
- if timeout and time_elapsed >= timeout:
|
|
|
|
- raise TimeoutError('The operation timed out.')
|
|
|
|
-
|
|
|
|
def prepare_expires(self, value, type=None):
|
|
def prepare_expires(self, value, type=None):
|
|
if value is None:
|
|
if value is None:
|
|
value = self.app.conf.result_expires
|
|
value = self.app.conf.result_expires
|
|
@@ -406,9 +370,247 @@ class BaseBackend(object):
|
|
|
|
|
|
def __reduce__(self, args=(), kwargs={}):
|
|
def __reduce__(self, args=(), kwargs={}):
|
|
return (unpickle_backend, (self.__class__, args, kwargs))
|
|
return (unpickle_backend, (self.__class__, args, kwargs))
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class SyncBackendMixin(object):
|
|
|
|
+
|
|
|
|
+ def iter_native(self, result, timeout=None, interval=0.5, no_ack=True,
|
|
|
|
+ on_message=None, on_interval=None):
|
|
|
|
+ results = result.results
|
|
|
|
+ if not results:
|
|
|
|
+ return iter([])
|
|
|
|
+ return self.get_many(
|
|
|
|
+ {r.id for r in results},
|
|
|
|
+ timeout=timeout, interval=interval, no_ack=no_ack,
|
|
|
|
+ on_message=on_message, on_interval=on_interval,
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ def wait_for_pending(self, result, timeout=None, interval=0.5,
|
|
|
|
+ no_ack=True, on_interval=None, callback=None,
|
|
|
|
+ propagate=True):
|
|
|
|
+ meta = self.wait_for(
|
|
|
|
+ result.id, timeout=timeout,
|
|
|
|
+ interval=interval,
|
|
|
|
+ on_interval=on_interval,
|
|
|
|
+ no_ack=no_ack,
|
|
|
|
+ )
|
|
|
|
+ if meta:
|
|
|
|
+ result._maybe_set_cache(meta)
|
|
|
|
+ return result.maybe_throw(propagate=propagate, callback=callback)
|
|
|
|
+
|
|
|
|
+ def wait_for(self, task_id,
|
|
|
|
+ timeout=None, interval=0.5, no_ack=True, on_interval=None):
|
|
|
|
+ """Wait for task and return its result.
|
|
|
|
+
|
|
|
|
+ If the task raises an exception, this exception
|
|
|
|
+ will be re-raised by :func:`wait_for`.
|
|
|
|
+
|
|
|
|
+ If `timeout` is not :const:`None`, this raises the
|
|
|
|
+ :class:`celery.exceptions.TimeoutError` exception if the operation
|
|
|
|
+ takes longer than `timeout` seconds.
|
|
|
|
+
|
|
|
|
+ """
|
|
|
|
+
|
|
|
|
+ time_elapsed = 0.0
|
|
|
|
+
|
|
|
|
+ while 1:
|
|
|
|
+ meta = self.get_task_meta(task_id)
|
|
|
|
+ if meta['status'] in states.READY_STATES:
|
|
|
|
+ return meta
|
|
|
|
+ if on_interval:
|
|
|
|
+ on_interval()
|
|
|
|
+ # avoid hammering the CPU checking status.
|
|
|
|
+ time.sleep(interval)
|
|
|
|
+ time_elapsed += interval
|
|
|
|
+ if timeout and time_elapsed >= timeout:
|
|
|
|
+ raise TimeoutError('The operation timed out.')
|
|
|
|
+
|
|
|
|
+ def add_pending_result(self, result):
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+ def remove_pending_result(self, result):
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class AsyncBackendMixin(object):
|
|
|
|
+
|
|
|
|
+ def _collect_into(self, result, bucket):
|
|
|
|
+ self.result_consumer.buckets[result] = bucket
|
|
|
|
+
|
|
|
|
+ def iter_native(self, result, timeout=None, interval=0.5, no_ack=True,
|
|
|
|
+ on_message=None, on_interval=None):
|
|
|
|
+ results = result.results
|
|
|
|
+ if not results:
|
|
|
|
+ raise StopIteration()
|
|
|
|
+
|
|
|
|
+ bucket = deque()
|
|
|
|
+ for result in results:
|
|
|
|
+ self._collect_into(result, bucket)
|
|
|
|
+
|
|
|
|
+ for _ in self._wait_for_pending(
|
|
|
|
+ result,
|
|
|
|
+ timeout=timeout, interval=interval, no_ack=no_ack,
|
|
|
|
+ on_message=on_message, on_interval=on_interval):
|
|
|
|
+ while bucket:
|
|
|
|
+ result = bucket.popleft()
|
|
|
|
+ yield result.id, result._cache
|
|
|
|
+ while bucket:
|
|
|
|
+ result = bucket.popleft()
|
|
|
|
+ yield result.id, result._cache
|
|
|
|
+
|
|
|
|
+ def add_pending_result(self, result):
|
|
|
|
+ if result.id not in self._pending_results:
|
|
|
|
+ self._pending_results[result.id] = result
|
|
|
|
+ self.result_consumer.consume_from(self._create_binding(result.id))
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+ def remove_pending_result(self, result):
|
|
|
|
+ self._pending_results.pop(result.id, None)
|
|
|
|
+ self.on_result_fulfilled(result)
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+ def on_result_fulfilled(self, result):
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+ def wait_for_pending(self, result,
|
|
|
|
+ callback=None, propagate=True, **kwargs):
|
|
|
|
+ for _ in self._wait_for_pending(result, **kwargs):
|
|
|
|
+ pass
|
|
|
|
+ return result.maybe_throw(callback=callback, propagate=propagate)
|
|
|
|
+
|
|
|
|
+ def _wait_for_pending(self, result, timeout=None, interval=0.5,
|
|
|
|
+ no_ack=True, on_interval=None, on_message=None,
|
|
|
|
+ callback=None, propagate=True):
|
|
|
|
+ return self.result_consumer._wait_for_pending(
|
|
|
|
+ result, timeout=timeout, interval=interval,
|
|
|
|
+ no_ack=no_ack, on_interval=on_interval,
|
|
|
|
+ callback=callback, on_message=on_message, propagate=propagate,
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class BaseBackend(Backend, SyncBackendMixin):
|
|
|
|
+ pass
|
|
BaseDictBackend = BaseBackend # XXX compat
|
|
BaseDictBackend = BaseBackend # XXX compat
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
+class Drainer(object):
|
|
|
|
+
|
|
|
|
+ def __init__(self, result_consumer):
|
|
|
|
+ self.result_consumer = result_consumer
|
|
|
|
+
|
|
|
|
+ def drain_events_until(self, p, timeout=None, on_interval=None,
|
|
|
|
+ monotonic=monotonic, wait=None):
|
|
|
|
+ wait = wait or self.result_consumer.drain_events
|
|
|
|
+ time_start = monotonic()
|
|
|
|
+
|
|
|
|
+ while 1:
|
|
|
|
+ # Total time spent may exceed a single call to wait()
|
|
|
|
+ if timeout and monotonic() - time_start >= timeout:
|
|
|
|
+ raise socket.timeout()
|
|
|
|
+ try:
|
|
|
|
+ yield self.wait_for(p, wait, timeout=1)
|
|
|
|
+ except socket.timeout:
|
|
|
|
+ pass
|
|
|
|
+ if on_interval:
|
|
|
|
+ on_interval()
|
|
|
|
+ if p.ready: # got event on the wanted channel.
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+ def wait_for(self, p, wait, timeout=None):
|
|
|
|
+ wait(timeout=timeout)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class EventletDrainer(Drainer):
|
|
|
|
+ _g = None
|
|
|
|
+ _stopped = False
|
|
|
|
+
|
|
|
|
+ def run(self):
|
|
|
|
+ while not self._stopped:
|
|
|
|
+ try:
|
|
|
|
+ print("DRAINING!!!!!!!!!!!!!!!!")
|
|
|
|
+ self.result_consumer.drain_events(timeout=10)
|
|
|
|
+ except socket.timeout:
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+ def start(self):
|
|
|
|
+ from eventlet import spawn
|
|
|
|
+ if self._g is None:
|
|
|
|
+ self._g = spawn(self.run)
|
|
|
|
+
|
|
|
|
+ def stop(self):
|
|
|
|
+ self._stopped = True
|
|
|
|
+
|
|
|
|
+ def wait_for(self, p, wait, timeout=None):
|
|
|
|
+ if self._g is None:
|
|
|
|
+ self.start()
|
|
|
|
+ if not p.ready:
|
|
|
|
+ time.sleep(0)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+drainers = {'default': Drainer, 'eventlet': EventletDrainer}
|
|
|
|
+
|
|
|
|
+class BaseResultConsumer(object):
|
|
|
|
+
|
|
|
|
+ def __init__(self, backend, app, accept, pending_results):
|
|
|
|
+ self.backend = backend
|
|
|
|
+ self.app = app
|
|
|
|
+ self.accept = accept
|
|
|
|
+ self._pending_results = pending_results
|
|
|
|
+ self.on_message = None
|
|
|
|
+ self.buckets = WeakKeyDictionary()
|
|
|
|
+ self.drainer = drainers[detect_environment()](self)
|
|
|
|
+
|
|
|
|
+ def drain_events(self, timeout=None):
|
|
|
|
+ raise NotImplementedError('subclass responsibility')
|
|
|
|
+
|
|
|
|
+ def _after_fork(self):
|
|
|
|
+ self.bucket.clear()
|
|
|
|
+ self.buckets = WeakKeyDictionary()
|
|
|
|
+ self.on_message = None
|
|
|
|
+ self.on_after_fork()
|
|
|
|
+
|
|
|
|
+ def on_after_fork(self):
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+ def drain_events_until(self, p, timeout=None, on_interval=None):
|
|
|
|
+ return self.drainer.drain_events_until(
|
|
|
|
+ p, timeout=timeout, on_interval=on_interval)
|
|
|
|
+
|
|
|
|
+ def _wait_for_pending(self, result, timeout=None, interval=0.5,
|
|
|
|
+ no_ack=True, on_interval=None, callback=None,
|
|
|
|
+ on_message=None, propagate=True):
|
|
|
|
+ prev_on_m, self.on_message = self.on_message, on_message
|
|
|
|
+ try:
|
|
|
|
+ for _ in self.drain_events_until(
|
|
|
|
+ result.on_ready, timeout=timeout,
|
|
|
|
+ on_interval=on_interval):
|
|
|
|
+ yield
|
|
|
|
+ time.sleep(0)
|
|
|
|
+ except socket.timeout:
|
|
|
|
+ raise TimeoutError('The operation timed out.')
|
|
|
|
+ finally:
|
|
|
|
+ self.on_message = prev_on_m
|
|
|
|
+
|
|
|
|
+ def on_state_change(self, meta, message):
|
|
|
|
+ if self.on_message:
|
|
|
|
+ self.on_message(meta)
|
|
|
|
+ if meta['status'] in states.READY_STATES:
|
|
|
|
+ try:
|
|
|
|
+ result = self._pending_results[meta['task_id']]
|
|
|
|
+ except KeyError:
|
|
|
|
+ return
|
|
|
|
+ result._maybe_set_cache(meta)
|
|
|
|
+ buckets = self.buckets
|
|
|
|
+ try:
|
|
|
|
+ buckets[result].append(result)
|
|
|
|
+ buckets.pop(result)
|
|
|
|
+ except KeyError:
|
|
|
|
+ pass
|
|
|
|
+ time.sleep(0)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
class KeyValueStoreBackend(BaseBackend):
|
|
class KeyValueStoreBackend(BaseBackend):
|
|
key_t = ensure_bytes
|
|
key_t = ensure_bytes
|
|
task_keyprefix = 'celery-task-meta-'
|
|
task_keyprefix = 'celery-task-meta-'
|