Skip to content

Fix HTTP1Connection reuse crash after request outlives response - #929

Merged
fabianfett merged 1 commit into
swift-server:mainfrom
fabianfett:ff-regression-879
Sep 25, 2026
Merged

fabianfett merged 1 commit into
swift-server:mainfrom
fabianfett:ff-regression-879

Conversation

@fabianfett

Copy link
Copy Markdown
Member

Motivation

If the response finishes before the request body stream, we use the write promise of the request's .end to tell the connection pool that the connection can be reused. This write promise can be completed synchronously inside writeAndFlush. In that case the pool may schedule the next request onto the connection before writeAndFlush returns. Afterwards we transitioned the idle read/write timeout state machines, which by then belonged to the new request. With an idle read timeout configured, this crashes with:

  HTTP1ClientChannelHandler.swift: Fatal error: Invalid state. Waiting for response data must start after request head was sent

This is a regression introduced in 1.31.0 (#879).

Changes

  • Update the idle read and write timeout state machines before writing and flushing the request's .end, so they are always applied to the request that is actually ending.
  • Add a Swift Testing reproducer using EmbeddedChannel that schedules a new request from onConnectionIdle while the .end write completes.
  • Allow MockHTTPExecutableRequest to be created with custom RequestOptions.

Result

A request whose response finishes before its body stream no longer crashes the connection when the pool immediately reuses that connection.

### Motivation

If the response finishes before the request body stream, we use the write promise of the request's `.end` to tell the connection pool that the connection can be reused. This write promise can be completed synchronously inside `writeAndFlush`. In that case the pool may schedule the next request onto the connection before `writeAndFlush` returns. Afterwards we transitioned the idle read/write timeout state machines, which by then belonged to the *new* request. With an idle read timeout configured, this crashes with:

      HTTP1ClientChannelHandler.swift: Fatal error: Invalid state. Waiting for response data must start after request head was sent

This is a regression introduced in 1.31.0 (swift-server#879).

### Changes

- Update the idle read and write timeout state machines *before* writing and flushing the request's `.end`, so they are always applied to the request that is actually ending.
- Add a Swift Testing reproducer using `EmbeddedChannel` that schedules a new request from `onConnectionIdle` while the `.end` write completes.
- Allow `MockHTTPExecutableRequest` to be created with custom `RequestOptions`.

### Result

A request whose response finishes before its body stream no longer crashes the connection when the pool immediately reuses that connection.
@fabianfett fabianfett added the 🔨 semver/patch No public API change. label Sep 25, 2026

@weissi weissi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good.

State reconciliation before callouts to arbitrary code is definitely the correct choice here.

@fabianfett
fabianfett merged commit 4c005f9 into swift-server:main Sep 25, 2026
36 checks passed
@fabianfett
fabianfett deleted the ff-regression-879 branch September 25, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants