Repository navigation
AsyncFuture background polling failures leave completion callbacks pending #18862
Description
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