Skip to content

unpack_all*()/await promise can loop forever on cyclic Promise resolution #101

Description

@coderabbitai

Summary

Both full-unpack paths in promising/promise.py follow nested Promise results without tracking previously seen promises. If a promise resolves to itself or back to an earlier promise in the chain, unpack_all_sync() and await promise/unpack_all() will spin forever or recurse until they blow up instead of raising a PromisingError.

Affected code

  • Promise.unpack_all_sync() — lines ~260-297
  • _AwaitablePromiseUnpacker.__await__() (the loop used by await promise/unpack_all()) — lines ~668-689

Suggested fix

Add a seen set guard in both unpack-all loops:

# unpack_all_sync
def unpack_all_sync(self, *, timeout: float | None = None) -> T_co:
    deadline = None if timeout is None else time.monotonic() + timeout
    result = self.as_concurrent_future().result(timeout=timeout)
    seen: set[int] = {id(self)}

    while isinstance(result, Promise):
        if id(result) in seen:
            raise PromisingError("Cyclic promise resolution detected")
        seen.add(id(result))

        remaining = None if deadline is None else deadline - time.monotonic()
        if remaining is not None:
            remaining = max(remaining, 0)

        result = result.as_concurrent_future().result(timeout=remaining)

    return result
# _AwaitablePromiseUnpacker.__await__
def __await__(self) -> Generator[Any, None, T_co | Promise[T_co]]:
    seen: set[int] = {id(self._promise)}
    ...
    if self._unpack_all:
        while isinstance(result, Promise):
            if id(result) in seen:
                raise PromisingError("Cyclic promise resolution detected")
            seen.add(id(result))
            result = yield from result.__await__()

    return result

Origin

Flagged as an outside-diff-range comment in PR #93 (review run 5f7583e4-f8cc-417e-b8e8-81118bf59de3, commit d00143025be5aa65f9ebb18b02de0b1bfe8783b2).

PR: #93
Requested by: @teremterem

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions