Skip to content

AsyncFuture background polling failures leave completion callbacks pending #18862

Open

Description

@JackyHsu-X

Determine this is the right repository

  • I determined this is the correct repository in which to report this bug.

Summary of the issue

AsyncFuture.add_done_callback() starts a background polling task. If polling raises a non-retryable exception, that task fails but the underlying future remains pending. The registered completion callbacks never run. For example, a long-running operation whose refresh RPC returns Forbidden leaves a callback-based caller waiting for a completion notification that never arrives.

API client name and version

google-api-core 2.43.0 from main at 598c1af54b2ce5cfdee520ca38514c2b8638959b (packages/google-api-core, handwritten code).

Reproduction steps: code

import asyncio

from google.api_core import exceptions, operation_async
from google.longrunning import operations_pb2
from google.protobuf import struct_pb2


async def main():
    async def refresh(**kwargs):
        raise exceptions.Forbidden("polling permission denied")

    async def cancel():
        pass

    operation = operation_async.AsyncOperation(
        operations_pb2.Operation(name="operations/example"),
        refresh,
        cancel,
        result_type=struct_pb2.Struct,
    )
    callbacks = []
    operation.add_done_callback(callbacks.append)
    # Inspect the background task only to make this reproduction deterministic
    # and retrieve its exception instead of leaving an unhandled-task warning.
    outcomes = await asyncio.gather(operation._background_task, return_exceptions=True)
    await asyncio.sleep(0)
    print("poll task outcome:", repr(outcomes[0]))
    print("callback invoked:", bool(callbacks))
    print("operation future resolved:", operation._future.done())


asyncio.run(main())

Reproduction steps: supporting files

None. No credentials, network access, or Cloud project are needed.

Reproduction steps: actual results

poll task outcome: Forbidden('polling permission denied')
callback invoked: False
operation future resolved: False

Reproduction steps: expected results

The background polling failure should reach the future and invoke the completion callbacks, allowing callers to observe the exception. Foreground result()/exception() polling should retain its existing exception behavior. Cancellation should remain separate from an ordinary polling failure.

OS & version + platform

Windows x86_64.

Python environment

CPython 3.13.15.

Python dependencies

google-api-core 2.43.0 (source), googleapis-common-protos 1.76.0, protobuf 7.36.2, grpcio 1.85.0, google-auth 2.62.0.

Additional context

The background task created in google/api_core/future/async_future.py has no handler that transfers its failure to self._future. I plan to prepare a focused fix and regression tests for that path.

AI assistance: OpenAI Codex investigated and ran this reproduction, and drafted this report under the account owner's authorization. No separate human review is claimed.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions