Exemple #1
0
        def _wrapped(*args: Any, **kwargs: Any) -> Any:
            # If we're passed a cache_context then we'll want to call its invalidate()
            # whenever we are invalidated
            invalidate_callback = kwargs.pop("on_invalidate", None)

            cache_key = get_cache_key(args, kwargs)

            try:
                ret = cache.get(cache_key, callback=invalidate_callback)
            except KeyError:
                # Add our own `cache_context` to argument list if the wrapped function
                # has asked for one
                if self.add_cache_context:
                    kwargs["cache_context"] = _CacheContext.get_instance(
                        cache, cache_key)

                ret = defer.maybeDeferred(preserve_fn(self.orig), obj, *args,
                                          **kwargs)
                ret = cache.set(cache_key, ret, callback=invalidate_callback)

                # We started a new call to `self.orig`, so we must always wait for it to
                # complete. Otherwise we might mark our current logging context as
                # finished while `self.orig` is still using it in the background.
                ret = delay_cancellation(ret)

            return make_deferred_yieldable(ret)
    def test_coroutine_cancellation(self):
        """Test that cancellation of the new `Deferred` waits for the original."""
        blocking_deferred: "Deferred[None]" = Deferred()
        completion_deferred: "Deferred[None]" = Deferred()

        async def task():
            await blocking_deferred
            completion_deferred.callback(None)
            # Raise an exception. Twisted should consume it, otherwise unwanted
            # tracebacks will be printed in logs.
            raise ValueError("abc")

        wrapper_deferred = delay_cancellation(task())

        # Cancel the new `Deferred`.
        wrapper_deferred.cancel()
        self.assertNoResult(wrapper_deferred)
        self.assertFalse(
            blocking_deferred.called, "Cancellation was propagated too deep"
        )
        self.assertFalse(completion_deferred.called)

        # Unblock the task.
        blocking_deferred.callback(None)
        self.assertTrue(completion_deferred.called)

        # Now that the original coroutine has failed, we should get a `CancelledError`.
        self.failureResultOf(wrapper_deferred, CancelledError)
 def wrap_deferred(self, deferred: "Deferred[str]") -> "Deferred[str]":
     if self.wrapper == "stop_cancellation":
         return stop_cancellation(deferred)
     elif self.wrapper == "delay_cancellation":
         return delay_cancellation(deferred)
     else:
         raise ValueError(f"Unsupported wrapper type: {self.wrapper}")
    def test_propagates_cancelled_error(self):
        """Test that a `CancelledError` from the original `Deferred` gets propagated."""
        deferred: "Deferred[str]" = Deferred()
        wrapper_deferred = delay_cancellation(deferred)

        # Fail the original `Deferred` with a `CancelledError`.
        cancelled_error = CancelledError()
        deferred.errback(cancelled_error)

        # The new `Deferred` should fail with exactly the same `CancelledError`.
        self.assertTrue(wrapper_deferred.called)
        self.assertIs(cancelled_error, self.failureResultOf(wrapper_deferred).value)
    def test_deferred_cancellation(self):
        """Test that cancellation of the new `Deferred` waits for the original."""
        deferred: "Deferred[str]" = Deferred()
        wrapper_deferred = delay_cancellation(deferred)

        # Cancel the new `Deferred`.
        wrapper_deferred.cancel()
        self.assertNoResult(wrapper_deferred)
        self.assertFalse(
            deferred.called, "Original `Deferred` was unexpectedly cancelled"
        )

        # Now make the original `Deferred` fail.
        # The `Failure` must be consumed, otherwise unwanted tracebacks will be printed
        # in logs.
        deferred.errback(ValueError("abc"))
        self.assertIsNone(deferred.result, "`Failure` was not consumed")

        # Now that the original `Deferred` has failed, we should get a `CancelledError`.
        self.failureResultOf(wrapper_deferred, CancelledError)
Exemple #6
0
        def wrapped(*args: Any, **kwargs: Any) -> "defer.Deferred[Dict]":
            # If we're passed a cache_context then we'll want to call its
            # invalidate() whenever we are invalidated
            invalidate_callback = kwargs.pop("on_invalidate", None)

            arg_dict = inspect.getcallargs(self.orig, obj, *args, **kwargs)
            keyargs = [arg_dict[arg_nm] for arg_nm in self.arg_names]
            list_args = arg_dict[self.list_name]

            results = {}

            def update_results_dict(res: Any, arg: Hashable) -> None:
                results[arg] = res

            # list of deferreds to wait for
            cached_defers = []

            missing = set()

            # If the cache takes a single arg then that is used as the key,
            # otherwise a tuple is used.
            if num_args == 1:

                def arg_to_cache_key(arg: Hashable) -> Hashable:
                    return arg

            else:
                keylist = list(keyargs)

                def arg_to_cache_key(arg: Hashable) -> Hashable:
                    keylist[self.list_pos] = arg
                    return tuple(keylist)

            for arg in list_args:
                try:
                    res = cache.get(arg_to_cache_key(arg),
                                    callback=invalidate_callback)
                    if not res.called:
                        res.addCallback(update_results_dict, arg)
                        cached_defers.append(res)
                    else:
                        results[arg] = res.result
                except KeyError:
                    missing.add(arg)

            if missing:
                # we need a deferred for each entry in the list,
                # which we put in the cache. Each deferred resolves with the
                # relevant result for that key.
                deferreds_map = {}
                for arg in missing:
                    deferred: "defer.Deferred[Any]" = defer.Deferred()
                    deferreds_map[arg] = deferred
                    key = arg_to_cache_key(arg)
                    cached_defers.append(
                        cache.set(key, deferred, callback=invalidate_callback))

                def complete_all(res: Dict[Hashable, Any]) -> None:
                    # the wrapped function has completed. It returns a dict.
                    # We can now update our own result map, and then resolve the
                    # observable deferreds in the cache.
                    for e, d1 in deferreds_map.items():
                        val = res.get(e, None)
                        # make sure we update the results map before running the
                        # deferreds, because as soon as we run the last deferred, the
                        # gatherResults() below will complete and return the result
                        # dict to our caller.
                        results[e] = val
                        d1.callback(val)

                def errback_all(f: Failure) -> None:
                    # the wrapped function has failed. Propagate the failure into
                    # the cache, which will invalidate the entry, and cause the
                    # relevant cached_deferreds to fail, which will propagate the
                    # failure to our caller.
                    for d1 in deferreds_map.values():
                        d1.errback(f)

                args_to_call = dict(arg_dict)
                args_to_call[self.list_name] = missing

                # dispatch the call, and attach the two handlers
                defer.maybeDeferred(preserve_fn(self.orig),
                                    **args_to_call).addCallbacks(
                                        complete_all, errback_all)

            if cached_defers:
                d = defer.gatherResults(cached_defers,
                                        consumeErrors=True).addCallbacks(
                                            lambda _: results,
                                            unwrapFirstError)
                if missing:
                    # We started a new call to `self.orig`, so we must always wait for it to
                    # complete. Otherwise we might mark our current logging context as
                    # finished while `self.orig` is still using it in the background.
                    d = delay_cancellation(d)
                return make_deferred_yieldable(d)
            else:
                return defer.succeed(results)