Skip to content

fix(smithy-core): guard cancelled request future in event stream pipeline#730

Open
valter-silva-au wants to merge 1 commit into
smithy-lang:developfrom
valter-silva-au:fix/bidi-input-stream-cancellation-invalidstateerror
Open

fix(smithy-core): guard cancelled request future in event stream pipeline#730
valter-silva-au wants to merge 1 commit into
smithy-lang:developfrom
valter-silva-au:fix/bidi-input-stream-cancellation-invalidstateerror

Conversation

@valter-silva-au

Copy link
Copy Markdown

Issue #, if available: Relates to awslabs/aws-sdk-python#13 — same failure mode, in the upstream runtime where the code now lives.

Description of changes:

When an input or duplex event stream is invoked, RequestPipeline runs the request in a background task and resolves a request_future once the transport send has been kicked off, so the caller gets a stream that's ready to send. If the consumer awaiting that future is cancelled (the caller is cancelled, or times out), asyncio cancels the future. The still-running background task then reaches the unguarded request_future.set_result(...) on a now-cancelled future, which raises asyncio.InvalidStateError (surfaced as a SmithyError).

This guards the set:

if not request_future.cancelled():
    request_future.set_result(request_context)

This is the same remedy suggested upstream in awslabs/aws-sdk-python#13. That issue originally pointed at CRTResponseBody._on_complete in smithy_http, but that class was removed in #573 (the CRT client moved to the unified-stream API); the same race relocated to the request pipeline in smithy-core, which is the one remaining unguarded set_result in the runtime.

Notes:

  • Streaming operations are non-retryable (retryable() returns False when an input stream member is present), so set_result runs at most once — the failure is specifically the cancellation race, not a double-set. Guarding on cancelled() is therefore sufficient and precise.
  • Added a regression test (packages/smithy-core/tests/unit/aio/test_client.py) that drives the pipeline with a cancelled request future. Verified it fails (InvalidStateError) without the guard and passes with it. There were no prior unit tests for the pipeline; the test introduces minimal protocol/transport doubles.
  • Added a bugfix changelog fragment for smithy-core.

make check-py and make test-py pass locally (1482 passed, 3 skipped).

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…line

- input_stream/duplex_stream run the request in a background task and set
  the request future once the transport send starts. If the consumer
  awaiting that future is cancelled, asyncio cancels the future and the
  unguarded set_result raised asyncio.InvalidStateError.
- Guard the set with `if not request_future.cancelled()`, the same remedy
  suggested in awslabs/aws-sdk-python#13 (whose original CRTResponseBody
  site was removed by smithy-lang#573; the failure mode relocated here).
- Add a regression test driving the pipeline with a cancelled request
  future; verified RED without the guard, GREEN with it.
@valter-silva-au valter-silva-au requested a review from a team as a code owner June 27, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant