Esempio n. 1
0
    def unlock_chord(group_id, callback, interval=None, propagate=None,
                     max_retries=None, result=None,
                     Result=app.AsyncResult, GroupResult=app.GroupResult,
                     from_serializable=from_serializable):
        # if propagate is disabled exceptions raised by chord tasks
        # will be sent as part of the result list to the chord callback.
        # Since 3.1 propagate will be enabled by default, and instead
        # the chord callback changes state to FAILURE with the
        # exception set to ChordError.
        propagate = default_propagate if propagate is None else propagate

        # check if the task group is ready, and if so apply the callback.
        deps = GroupResult(
            group_id,
            [from_serializable(r, Result=Result) for r in result],
        )
        j = deps.join_native if deps.supports_native_join else deps.join

        if deps.ready():
            callback = subtask(callback)
            try:
                ret = j(propagate=propagate)
            except Exception, exc:
                culprit = deps._failed_join_report().next()
                app._tasks[callback.task].backend.fail_from_current_stack(
                    callback.id, exc=ChordError('Dependency %s raised %r' % (
                        culprit.id, exc)),
                )
            else:
                try:
                    callback.delay(ret)
                except Exception, exc:
                    app._tasks[callback.task].backend.fail_from_current_stack(
                        callback.id,
                        exc=ChordError('Call callback error: %r' % (exc, )))
Esempio n. 2
0
def trigger_callback(app, callback, group_result):
    """Add the callback to the queue or mark the callback as failed

    Implementation borrowed from `celery.app.builtins.unlock_chord`
    """
    j = (
        group_result.join_native
        if group_result.supports_native_join
        else group_result.join
    )

    try:
        with allow_join_result():
            ret = j(timeout=app.conf.result_chord_join_timeout, propagate=True)
    except Exception as exc:  # pylint: disable=broad-except
        try:
            culprit = next(group_result._failed_join_report())
            reason = "Dependency {0.id} raised {1!r}".format(culprit, exc)
        except StopIteration:
            reason = repr(exc)
        logger.exception("Chord %r raised: %r", group_result.id, exc)
        app.backend.chord_error_from_stack(callback, ChordError(reason))
    else:
        try:
            callback.delay(ret)
        except Exception as exc:  # pylint: disable=broad-except
            logger.exception("Chord %r raised: %r", group_result.id, exc)
            app.backend.chord_error_from_stack(
                callback, exc=ChordError("Callback error: {0!r}".format(exc))
            )
Esempio n. 3
0
    def on_chord_part_return(self,
                             request,
                             state,
                             result,
                             propagate=None,
                             **kwargs):
        app = self.app
        tid, gid = request.id, request.group
        if not gid or not tid:
            return

        client = self.client
        jkey = self.get_key_for_group(gid, '.j')
        tkey = self.get_key_for_group(gid, '.t')
        result = self.encode_result(result, state)
        with client.pipeline() as pipe:
            _, readycount, totaldiff, _, _ = pipe \
                .rpush(jkey, self.encode([1, tid, state, result])) \
                .llen(jkey) \
                .get(tkey) \
                .expire(jkey, self.expires) \
                .expire(tkey, self.expires) \
                .execute()

        totaldiff = int(totaldiff or 0)

        try:
            callback = maybe_signature(request.chord, app=app)
            total = callback['chord_size'] + totaldiff
            if readycount == total:
                decode, unpack = self.decode, self._unpack_chord_result
                with client.pipeline() as pipe:
                    resl, = pipe \
                        .lrange(jkey, 0, total) \
                        .execute()
                try:
                    callback.delay([unpack(tup, decode) for tup in resl])
                    with client.pipeline() as pipe:
                        _, _ = pipe \
                            .delete(jkey) \
                            .delete(tkey) \
                            .execute()
                except Exception as exc:  # pylint: disable=broad-except
                    logger.exception('Chord callback for %r raised: %r',
                                     request.group, exc)
                    return self.chord_error_from_stack(
                        callback,
                        ChordError('Callback error: {0!r}'.format(exc)),
                    )

        except ChordError as exc:
            logger.exception('Chord %r raised: %r', request.group, exc)
            return self.chord_error_from_stack(callback, exc)
        except Exception as exc:  # pylint: disable=broad-except
            logger.exception('Chord %r raised: %r', request.group, exc)
            return self.chord_error_from_stack(
                callback,
                ChordError('Join error: {0!r}'.format(exc)),
            )
Esempio n. 4
0
    def unlock_chord(self,
                     group_id,
                     callback,
                     interval=None,
                     max_retries=None,
                     result=None,
                     Result=app.AsyncResult,
                     GroupResult=app.GroupResult,
                     result_from_tuple=result_from_tuple,
                     **kwargs):
        if interval is None:
            interval = self.default_retry_delay

        # check if the task group is ready, and if so apply the callback.
        callback = maybe_signature(callback, app)
        deps = GroupResult(
            group_id,
            [result_from_tuple(r, app=app) for r in result],
            app=app,
        )
        j = deps.join_native if deps.supports_native_join else deps.join

        try:
            ready = deps.ready()
        except Exception as exc:
            raise self.retry(
                exc=exc,
                countdown=interval,
                max_retries=max_retries,
            )
        else:
            if not ready:
                raise self.retry(countdown=interval, max_retries=max_retries)

        callback = maybe_signature(callback, app=app)
        try:
            with allow_join_result():
                ret = j(timeout=3.0, propagate=True)
        except Exception as exc:
            try:
                culprit = next(deps._failed_join_report())
                reason = 'Dependency {0.id} raised {1!r}'.format(
                    culprit,
                    exc,
                )
            except StopIteration:
                reason = repr(exc)
            logger.error('Chord %r raised: %r', group_id, exc, exc_info=1)
            app.backend.chord_error_from_stack(callback, ChordError(reason))
        else:
            try:
                callback.delay(ret)
            except Exception as exc:
                logger.error('Chord %r raised: %r', group_id, exc, exc_info=1)
                app.backend.chord_error_from_stack(
                    callback,
                    exc=ChordError('Callback error: {0!r}'.format(exc)),
                )
Esempio n. 5
0
    def unlock_chord(group_id,
                     callback,
                     interval=None,
                     propagate=None,
                     max_retries=None,
                     result=None,
                     Result=app.AsyncResult,
                     GroupResult=app.GroupResult,
                     result_from_tuple=result_from_tuple):
        # if propagate is disabled exceptions raised by chord tasks
        # will be sent as part of the result list to the chord callback.
        # Since 3.1 propagate will be enabled by default, and instead
        # the chord callback changes state to FAILURE with the
        # exception set to ChordError.
        propagate = default_propagate if propagate is None else propagate
        if interval is None:
            interval = unlock_chord.default_retry_delay

        # check if the task group is ready, and if so apply the callback.
        callback = maybe_signature(callback, app)
        deps = GroupResult(
            group_id,
            [result_from_tuple(r, app=app) for r in result],
        )
        j = deps.join_native if deps.supports_native_join else deps.join

        if deps.ready():
            callback = maybe_signature(callback, app=app)
            try:
                with allow_join_result():
                    ret = j(timeout=3.0, propagate=propagate)
            except Exception as exc:
                try:
                    culprit = next(deps._failed_join_report())
                    reason = 'Dependency {0.id} raised {1!r}'.format(
                        culprit,
                        exc,
                    )
                except StopIteration:
                    reason = repr(exc)
                logger.error('Chord %r raised: %r', group_id, exc, exc_info=1)
                app.backend.chord_error_from_stack(callback,
                                                   ChordError(reason))
            else:
                try:
                    callback.delay(ret)
                except Exception as exc:
                    logger.error('Chord %r raised: %r',
                                 group_id,
                                 exc,
                                 exc_info=1)
                    app.backend.chord_error_from_stack(
                        callback,
                        exc=ChordError('Callback error: {0!r}'.format(exc)),
                    )
        else:
            raise unlock_chord.retry(countdown=interval,
                                     max_retries=max_retries)
Esempio n. 6
0
    def on_chord_part_return(self, task, state, result, propagate=None):
        if not self.implements_incr:
            return
        app = self.app
        if propagate is None:
            propagate = app.conf.CELERY_CHORD_PROPAGATES
        gid = task.request.group
        if not gid:
            return
        key = self.get_key_for_chord(gid)
        try:
            deps = GroupResult.restore(gid, backend=task.backend)
        except Exception as exc:
            callback = maybe_signature(task.request.chord, app=app)
            return self.chord_error_from_stack(
                callback,
                ChordError('Cannot restore group: {0!r}'.format(exc)),
            )
        if deps is None:
            try:
                raise ValueError(gid)
            except ValueError as exc:
                callback = maybe_signature(task.request.chord, app=app)
                return self.chord_error_from_stack(
                    callback,
                    ChordError('GroupResult {0} no longer exists'.format(gid)),
                )
        val = self.incr(key)
        if val >= len(deps):
            callback = maybe_signature(task.request.chord, app=app)
            j = deps.join_native if deps.supports_native_join else deps.join
            try:
                with allow_join_result():
                    ret = j(timeout=3.0, propagate=propagate)
            except Exception as exc:
                try:
                    culprit = next(deps._failed_join_report())
                    reason = 'Dependency {0.id} raised {1!r}'.format(
                        culprit,
                        exc,
                    )
                except StopIteration:
                    reason = repr(exc)

                self.chord_error_from_stack(callback, ChordError(reason))
            else:
                try:
                    callback.delay(ret)
                except Exception as exc:
                    self.chord_error_from_stack(
                        callback,
                        ChordError('Callback error: {0!r}'.format(exc)),
                    )
            finally:
                deps.delete()
                self.client.delete(key)
        else:
            self.expire(key, 86400)
Esempio n. 7
0
    def _new_chord_return(self,
                          task,
                          state,
                          result,
                          propagate=None,
                          PROPAGATE_STATES=states.PROPAGATE_STATES):
        app = self.app
        if propagate is None:
            propagate = self.app.conf.CELERY_CHORD_PROPAGATES
        request = task.request
        tid, gid = request.id, request.group
        if not gid or not tid:
            return

        client = self.client
        jkey = self.get_key_for_group(gid, '.j')
        result = self.encode_result(result, state)
        with client.pipeline() as pipe:
            _, readycount, _ = pipe                                         \
                .rpush(jkey, self.encode([1, tid, state, result]))          \
                .llen(jkey)                                                 \
                .expire(jkey, 86400)                                        \
                .execute()

        try:
            callback = maybe_signature(request.chord, app=app)
            total = callback['chord_size']
            if readycount == total:
                decode, unpack = self.decode, self._unpack_chord_result
                with client.pipeline() as pipe:
                    resl, _, = pipe                 \
                        .lrange(jkey, 0, total)     \
                        .delete(jkey)               \
                        .execute()
                try:
                    callback.delay([unpack(tup, decode) for tup in resl])
                except Exception as exc:
                    error('Chord callback for %r raised: %r',
                          request.group,
                          exc,
                          exc_info=1)
                    app._tasks[callback.task].backend.fail_from_current_stack(
                        callback.id,
                        exc=ChordError('Callback error: {0!r}'.format(exc)),
                    )
        except ChordError as exc:
            error('Chord %r raised: %r', request.group, exc, exc_info=1)
            app._tasks[callback.task].backend.fail_from_current_stack(
                callback.id,
                exc=exc,
            )
        except Exception as exc:
            error('Chord %r raised: %r', request.group, exc, exc_info=1)
            app._tasks[callback.task].backend.fail_from_current_stack(
                callback.id,
                exc=ChordError('Join error: {0!r}'.format(exc)),
            )
Esempio n. 8
0
    def on_chord_part_return(self, request, state, result, propagate=None):
        app = self.app
        tid, gid = request.id, request.group
        if not gid or not tid:
            return

        client = self.client
        jkey = self.get_key_for_group(gid, '.j')
        tkey = self.get_key_for_group(gid, '.t')
        result = self.encode_result(result, state)
        with client.pipeline() as pipe:
            _, readycount, totaldiff, _, _ = pipe                           \
                .rpush(jkey, self.encode([1, tid, state, result]))          \
                .llen(jkey)                                                 \
                .get(tkey)                                                  \
                .expire(jkey, 86400)                                        \
                .expire(tkey, 86400)                                        \
                .execute()

        totaldiff = int(totaldiff or 0)

        try:
            callback = maybe_signature(request.chord, app=app)
            total = callback['chord_size'] + totaldiff
            if readycount == total:
                decode, unpack = self.decode, self._unpack_chord_result
                with client.pipeline() as pipe:
                    resl, _, _ = pipe               \
                        .lrange(jkey, 0, total)     \
                        .delete(jkey)               \
                        .delete(tkey)               \
                        .execute()
                try:
                    callback.delay([unpack(tup, decode) for tup in resl])
                except Exception as exc:
                    error('Chord callback for %r raised: %r',
                          request.group,
                          exc,
                          exc_info=1)
                    app._tasks[callback.task].backend.fail_from_current_stack(
                        callback.id,
                        exc=ChordError('Callback error: {0!r}'.format(exc)),
                    )
        except ChordError as exc:
            error('Chord %r raised: %r', request.group, exc, exc_info=1)
            app._tasks[callback.task].backend.fail_from_current_stack(
                callback.id,
                exc=exc,
            )
        except Exception as exc:
            error('Chord %r raised: %r', request.group, exc, exc_info=1)
            app._tasks[callback.task].backend.fail_from_current_stack(
                callback.id,
                exc=ChordError('Join error: {0!r}'.format(exc)),
            )
Esempio n. 9
0
    def unlock_chord(group_id,
                     callback,
                     interval=None,
                     propagate=None,
                     max_retries=None,
                     result=None,
                     Result=app.AsyncResult,
                     GroupResult=app.GroupResult,
                     from_serializable=from_serializable):
        # if propagate is disabled exceptions raised by chord tasks
        # will be sent as part of the result list to the chord callback.
        # Since 3.1 propagate will be enabled by default, and instead
        # the chord callback changes state to FAILURE with the
        # exception set to ChordError.
        propagate = default_propagate if propagate is None else propagate
        if interval is None:
            interval = unlock_chord.default_retry_delay

        # check if the task group is ready, and if so apply the callback.
        deps = GroupResult(
            group_id,
            [from_serializable(r, app=app) for r in result],
        )
        j = deps.join_native if deps.supports_native_join else deps.join

        if deps.ready():
            callback = subtask(callback)
            try:
                ret = j(propagate=propagate)
            except Exception as exc:
                try:
                    culprit = next(deps._failed_join_report())
                    reason = 'Dependency {0.id} raised {1!r}'.format(
                        culprit,
                        exc,
                    )
                except StopIteration:
                    reason = repr(exc)

                app._tasks[callback.task].backend.fail_from_current_stack(
                    callback.id,
                    exc=ChordError(reason),
                )
            else:
                try:
                    callback.delay(ret)
                except Exception as exc:
                    app._tasks[callback.task].backend.fail_from_current_stack(
                        callback.id,
                        exc=ChordError('Callback error: {0!r}'.format(exc)),
                    )
        else:
            return unlock_chord.retry(countdown=interval,
                                      max_retries=max_retries)
Esempio n. 10
0
 def _unpack_chord_result(self,
                          tup,
                          decode,
                          PROPAGATE_STATES=states.PROPAGATE_STATES):
     _, tid, state, retval = decode(tup)
     if state in PROPAGATE_STATES:
         raise ChordError('Dependency {0} raised {1!r}'.format(tid, retval))
     return retval
Esempio n. 11
0
    def on_chord_part_return(self, task, propagate=None):
        if not self.implements_incr:
            return
        from celery import subtask
        from celery.result import GroupResult
        app = self.app
        if propagate is None:
            propagate = self.app.conf.CELERY_CHORD_PROPAGATES
        gid = task.request.group
        if not gid:
            return
        key = self.get_key_for_chord(gid)
        deps = GroupResult.restore(gid, backend=task.backend)
        val = self.incr(key)
        if val >= len(deps):
            j = deps.join_native if deps.supports_native_join else deps.join
            callback = subtask(task.request.chord)
            try:
                ret = j(propagate=propagate)
            except Exception as exc:
                try:
                    culprit = next(deps._failed_join_report())
                    reason = 'Dependency {0.id} raised {1!r}'.format(
                        culprit,
                        exc,
                    )
                except StopIteration:
                    reason = repr(exc)

                app._tasks[callback.task].backend.fail_from_current_stack(
                    callback.id,
                    exc=ChordError(reason),
                )
            else:
                try:
                    callback.delay(ret)
                except Exception as exc:
                    app._tasks[callback.task].backend.fail_from_current_stack(
                        callback.id,
                        exc=ChordError('Callback error: {0!r}'.format(exc)),
                    )
            finally:
                deps.delete()
                self.client.delete(key)
        else:
            self.expire(key, 86400)
 def _unpack_chord_result(self, tup, decode,
                          EXCEPTION_STATES=states.EXCEPTION_STATES,
                          PROPAGATE_STATES=states.PROPAGATE_STATES):
     _, tid, state, retval = decode(tup)
     if state in EXCEPTION_STATES:
         retval = self.exception_to_python(retval)
     if state in PROPAGATE_STATES:
         raise ChordError('Dependency {0} raised {1!r}'.format(tid, retval))
     return retval
Esempio n. 13
0
    def execute_callback(self):
        """
        Execute serialized callback. Called via on_chord_part_return.

        There are no parameters, as everything we need is already serialized
        in the model somewhere.
        """
        callback_signature = Signature.from_dict(json.loads(self.serialized_callback))

        if any(result.status == FAILURE for result in self.completed_results.all()):
            # we either remove the failures and only return results from successful subtasks, or fail the entire chord
            if callback_signature.get('options', {}).get('propagate', current_app.conf.CELERY_CHORD_PROPAGATES):
                try:
                    raise ChordError(
                        "Error in subtasks! Ids: {}".format([
                            result.task_id
                            for result in self.completed_results.all()
                            if result.status == FAILURE
                        ])
                    )
                except ChordError as error:
                    self.mark_error(error, is_subtask=True)
                    return
            else:
                """
                Dev note: this doesn't *quite* match the behavior of the default backend.

                According to http://www.pythondoc.com/celery-3.1.11/configuration.html#celery-chord-propagates,
                the 2 options are to either propagate through (as done above), or to forward the Exception result
                into callback (versus this approach of dropping error results). It seems silly to ask callbacks to
                expect exception results as input though, so we drop them.
                """  # pylint: disable=pointless-string-statement
                for result in self.completed_results.all():
                    if result.status == FAILURE:
                        self.completed_results.remove(result)  # pylint: disable=no-member
                        result.delete()

        if callback_signature.get('options', {}).get('use_iterator', True):
            # If we're using an iterator, it's assumed to be because there are size concerns with the results
            # Thus, the callback_result TaskMeta object will have a null 'result' in the database, because you
            # stored those results someplace else as part of the callback function, right?
            try:
                callback_signature(self.completed_results.values_list('result', flat=True).iterator)
            except Exception as error:  # pylint: disable=broad-except
                self.mark_error(error, is_subtask=False)
                return
            else:
                self.callback_result.status = SUCCESS
                self.callback_result.date_done = datetime.now()
                self.callback_result.save()
        else:
            results_list = [subtask.result for subtask in self.completed_results.all()]
            callback_signature.id = self.callback_result.task_id
            callback_signature.apply_async((results_list, ), {})
Esempio n. 14
0
 def test_on_chord_part_return__ChordError(self):
     with self.chord_context(1) as (_, request, callback):
         self.b.client.pipeline = ContextMock()
         raise_on_second_call(self.b.client.pipeline, ChordError())
         self.b.client.pipeline.return_value.zadd().zcount().get().expire(
         ).expire().execute.return_value = (1, 1, 0, 4, 5)
         task = self.app._tasks['add'] = Mock(name='add_task')
         self.b.on_chord_part_return(request, states.SUCCESS, 10)
         task.backend.fail_from_current_stack.assert_called_with(
             callback.id, exc=ANY,
         )
Esempio n. 15
0
    def test_on_chord_part_return__ChordError__unordered(self):
        self.app.conf.result_backend_transport_options = dict(
            result_chord_ordered=False, )

        with self.chord_context(1) as (_, request, callback):
            self.b.client.pipeline = ContextMock()
            raise_on_second_call(self.b.client.pipeline, ChordError())
            self.b.client.pipeline.return_value.rpush().llen().get().expire(
            ).expire().execute.return_value = (1, 1, 0, 4, 5)
            task = self.app._tasks['add'] = Mock(name='add_task')
            self.b.on_chord_part_return(request, states.SUCCESS, 10)
            task.backend.fail_from_current_stack.assert_called_with(
                callback.id,
                exc=ANY,
            )
Esempio n. 16
0
    def on_chord_part_return(self, request, state, result, **kwargs):
        if not self.implements_incr:
            return
        app = self.app
        gid = request.group
        if not gid:
            return
        key = self.get_key_for_chord(gid)
        try:
            deps = GroupResult.restore(gid, backend=self)
        except Exception as exc:  # pylint: disable=broad-except
            callback = maybe_signature(request.chord, app=app)
            logger.exception('Chord %r raised: %r', gid, exc)
            return self.chord_error_from_stack(
                callback,
                ChordError(f'Cannot restore group: {exc!r}'),
            )
        if deps is None:
            try:
                raise ValueError(gid)
            except ValueError as exc:
                callback = maybe_signature(request.chord, app=app)
                logger.exception('Chord callback %r raised: %r', gid, exc)
                return self.chord_error_from_stack(
                    callback,
                    ChordError(f'GroupResult {gid} no longer exists'),
                )
        val = self.incr(key)
        # Set the chord size to the value defined in the request, or fall back
        # to the number of dependencies we can see from the restored result
        size = request.chord.get("chord_size")
        if size is None:
            size = len(deps)
        if val > size:  # pragma: no cover
            logger.warning('Chord counter incremented too many times for %r',
                           gid)
        elif val == size:
            callback = maybe_signature(request.chord, app=app)
            j = deps.join_native if deps.supports_native_join else deps.join
            try:
                with allow_join_result():
                    ret = j(timeout=app.conf.result_chord_join_timeout,
                            propagate=True)
            except Exception as exc:  # pylint: disable=broad-except
                try:
                    culprit = next(deps._failed_join_report())
                    reason = 'Dependency {0.id} raised {1!r}'.format(
                        culprit,
                        exc,
                    )
                except StopIteration:
                    reason = repr(exc)

                logger.exception('Chord %r raised: %r', gid, reason)
                self.chord_error_from_stack(callback, ChordError(reason))
            else:
                try:
                    callback.delay(ret)
                except Exception as exc:  # pylint: disable=broad-except
                    logger.exception('Chord %r raised: %r', gid, exc)
                    self.chord_error_from_stack(
                        callback,
                        ChordError(f'Callback error: {exc!r}'),
                    )
            finally:
                deps.delete()
                self.client.delete(key)
        else:
            self.expire(key, self.expires)
Esempio n. 17
0
    def on_chord_part_return(self, request, state, result, **kwargs):
        if not self.implements_incr:
            return
        app = self.app
        gid = request.group
        if not gid:
            return
        key = self.get_key_for_chord(gid)
        try:
            deps = GroupResult.restore(gid, backend=self)
        except Exception as exc:
            callback = maybe_signature(request.chord, app=app)
            logger.error('Chord %r raised: %r', gid, exc, exc_info=1)
            return self.chord_error_from_stack(
                callback,
                ChordError('Cannot restore group: {0!r}'.format(exc)),
            )
        if deps is None:
            try:
                raise ValueError(gid)
            except ValueError as exc:
                callback = maybe_signature(request.chord, app=app)
                logger.error('Chord callback %r raised: %r',
                             gid,
                             exc,
                             exc_info=1)
                return self.chord_error_from_stack(
                    callback,
                    ChordError('GroupResult {0} no longer exists'.format(gid)),
                )
        val = self.incr(key)
        size = len(deps)
        if val > size:  # pragma: no cover
            logger.warning('Chord counter incremented too many times for %r',
                           gid)
        elif val == size:
            callback = maybe_signature(request.chord, app=app)
            j = deps.join_native if deps.supports_native_join else deps.join
            try:
                with allow_join_result():
                    ret = j(timeout=3.0, propagate=True)
            except Exception as exc:
                try:
                    culprit = next(deps._failed_join_report())
                    reason = 'Dependency {0.id} raised {1!r}'.format(
                        culprit,
                        exc,
                    )
                except StopIteration:
                    reason = repr(exc)

                logger.error('Chord %r raised: %r', gid, reason, exc_info=1)
                self.chord_error_from_stack(callback, ChordError(reason))
            else:
                try:
                    callback.delay(ret)
                except Exception as exc:
                    logger.error('Chord %r raised: %r', gid, exc, exc_info=1)
                    self.chord_error_from_stack(
                        callback,
                        ChordError('Callback error: {0!r}'.format(exc)),
                    )
            finally:
                deps.delete()
                self.client.delete(key)
        else:
            self.expire(key, 86400)
Esempio n. 18
0
    def on_chord_part_return(self,
                             request,
                             state,
                             result,
                             propagate=None,
                             **kwargs):
        app = self.app
        tid, gid, group_index = request.id, request.group, request.group_index
        if not gid or not tid:
            return
        if group_index is None:
            group_index = '+inf'

        client = self.client
        jkey = self.get_key_for_group(gid, '.j')
        tkey = self.get_key_for_group(gid, '.t')
        skey = self.get_key_for_group(gid, '.s')
        result = self.encode_result(result, state)
        encoded = self.encode([1, tid, state, result])
        with client.pipeline() as pipe:
            pipeline = (pipe.zadd(jkey, {
                encoded: group_index
            }).zcount(jkey, "-inf",
                      "+inf") if self._chord_zset else pipe.rpush(
                          jkey, encoded).llen(jkey)).get(tkey).get(skey)
            if self.expires:
                pipeline = pipeline \
                    .expire(jkey, self.expires) \
                    .expire(tkey, self.expires) \
                    .expire(skey, self.expires)

            _, readycount, totaldiff, chord_size_bytes = pipeline.execute()[:4]

        totaldiff = int(totaldiff or 0)

        if chord_size_bytes:
            try:
                callback = maybe_signature(request.chord, app=app)
                total = int(chord_size_bytes) + totaldiff
                if readycount == total:
                    header_result = GroupResult.restore(gid)
                    if header_result is not None:
                        # If we manage to restore a `GroupResult`, then it must
                        # have been complex and saved by `apply_chord()` earlier.
                        #
                        # Before we can join the `GroupResult`, it needs to be
                        # manually marked as ready to avoid blocking
                        header_result.on_ready()
                        # We'll `join()` it to get the results and ensure they are
                        # structured as intended rather than the flattened version
                        # we'd construct without any other information.
                        join_func = (header_result.join_native
                                     if header_result.supports_native_join else
                                     header_result.join)
                        with allow_join_result():
                            resl = join_func(
                                timeout=app.conf.result_chord_join_timeout,
                                propagate=True)
                    else:
                        # Otherwise simply extract and decode the results we
                        # stashed along the way, which should be faster for large
                        # numbers of simple results in the chord header.
                        decode, unpack = self.decode, self._unpack_chord_result
                        with client.pipeline() as pipe:
                            if self._chord_zset:
                                pipeline = pipe.zrange(jkey, 0, -1)
                            else:
                                pipeline = pipe.lrange(jkey, 0, total)
                            resl, = pipeline.execute()
                        resl = [unpack(tup, decode) for tup in resl]
                    try:
                        callback.delay(resl)
                    except Exception as exc:  # pylint: disable=broad-except
                        logger.exception('Chord callback for %r raised: %r',
                                         request.group, exc)
                        return self.chord_error_from_stack(
                            callback,
                            ChordError(f'Callback error: {exc!r}'),
                        )
                    finally:
                        with client.pipeline() as pipe:
                            pipe \
                                .delete(jkey) \
                                .delete(tkey) \
                                .delete(skey) \
                                .execute()
            except ChordError as exc:
                logger.exception('Chord %r raised: %r', request.group, exc)
                return self.chord_error_from_stack(callback, exc)
            except Exception as exc:  # pylint: disable=broad-except
                logger.exception('Chord %r raised: %r', request.group, exc)
                return self.chord_error_from_stack(
                    callback,
                    ChordError(f'Join error: {exc!r}'),
                )
Esempio n. 19
0
    def on_chord_part_return(self, request, state, result,
                             propagate=None, **kwargs):
        app = self.app
        tid, gid, group_index = request.id, request.group, request.group_index
        if not gid or not tid:
            return
        if group_index is None:
            group_index = '+inf'

        client = self.client
        jkey = self.get_key_for_group(gid, '.j')
        tkey = self.get_key_for_group(gid, '.t')
        result = self.encode_result(result, state)
        encoded = self.encode([1, tid, state, result])
        with client.pipeline() as pipe:
            pipeline = (
                pipe.zadd(jkey, {encoded: group_index}).zcount(jkey, "-inf", "+inf")
                if self._chord_zset
                else pipe.rpush(jkey, encoded).llen(jkey)
            ).get(tkey)
            if self.expires is not None:
                pipeline = pipeline \
                    .expire(jkey, self.expires) \
                    .expire(tkey, self.expires)

            _, readycount, totaldiff = pipeline.execute()[:3]

        totaldiff = int(totaldiff or 0)

        try:
            callback = maybe_signature(request.chord, app=app)
            total = callback['chord_size'] + totaldiff
            if readycount == total:
                decode, unpack = self.decode, self._unpack_chord_result
                with client.pipeline() as pipe:
                    if self._chord_zset:
                        pipeline = pipe.zrange(jkey, 0, -1)
                    else:
                        pipeline = pipe.lrange(jkey, 0, total)
                    resl, = pipeline.execute()
                try:
                    callback.delay([unpack(tup, decode) for tup in resl])
                    with client.pipeline() as pipe:
                        _, _ = pipe \
                            .delete(jkey) \
                            .delete(tkey) \
                            .execute()
                except Exception as exc:  # pylint: disable=broad-except
                    logger.exception(
                        'Chord callback for %r raised: %r', request.group, exc)
                    return self.chord_error_from_stack(
                        callback,
                        ChordError(f'Callback error: {exc!r}'),
                    )
        except ChordError as exc:
            logger.exception('Chord %r raised: %r', request.group, exc)
            return self.chord_error_from_stack(callback, exc)
        except Exception as exc:  # pylint: disable=broad-except
            logger.exception('Chord %r raised: %r', request.group, exc)
            return self.chord_error_from_stack(
                callback,
                ChordError(f'Join error: {exc!r}'),
            )