Skip to content

Avoid precondition failure in write timeout#803

Merged
rnro merged 3 commits into
swift-server:mainfrom
rnro:write_timeout_crash
Jan 28, 2025
Merged

Avoid precondition failure in write timeout#803
rnro merged 3 commits into
swift-server:mainfrom
rnro:write_timeout_crash

Conversation

@rnro

@rnro rnro commented Jan 28, 2025

Copy link
Copy Markdown
Collaborator

Motivation:

In some cases we can crash because of a precondition failure when the write timeout fires and we aren't in the running state. This can happen for example if the connection is closed whilst the write timer is active.

Modifications:

  • Remove the precondition and instead take no action if the timeout fires outside of the running state. Instead we take a new Action, .noAction when the timer fires.
  • Clear write timeouts upon request completion. When a request completes we have no use for the idle write timer, we clear the read timer and we should clear the write one too.

Result:

Fewer crashes.

The supplied tests fails without these changes and passes with either of them.

@rnro rnro requested a review from fabianfett January 28, 2025 14:47
Motivation:

In some cases we can crash because of a precondition failure when the
write timeout fires and we aren't in the running state. This can happen
for example if the connection is closed whilst the write timer is
active.

Modifications:

Remove the precondition and instead take no action if the timeout fires
outside of the running state. Instead we take a new `Action`,
`.noAction` when the timer fires.

Result:

Fewer crashes.
@rnro rnro force-pushed the write_timeout_crash branch from 4d896b0 to 90e8b58 Compare January 28, 2025 14:57
@rnro rnro added the 🔨 semver/patch No public API change. label Jan 28, 2025
When a request completes we have no use for the idle write timer, we
clear the read timer and we should clear the write one too.
context.close(promise: nil)

case .wait:
case .wait, .noAction:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Do we need this new state or can we just re-use .wait?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

reuse .wait. .wait is a do nothing. can be renamed in a follow up pr.

@fabianfett fabianfett left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nits.

context.close(promise: nil)

case .wait:
case .wait, .noAction:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

reuse .wait. .wait is a do nothing. can be renamed in a follow up pr.

mutating func idleWriteTimeoutTriggered() -> Action {
guard case .inRequest(var requestStateMachine, let close) = self.state else {
preconditionFailure("Invalid state: \(self.state)")
return .noAction

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you mind adding another test to HTTP1ConnectionStateMachineTests that just tests this edge?

case fireChannelInactive
case fireChannelError(Error, closeConnection: Bool)

case noAction

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please remove.

XCTAssertEqual(request.events.map(\.kind), [.willExecuteRequest, .requestHeadSent])
}

class SlowHandler: ChannelOutboundHandler {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this isn't used at all.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah, a relic from a previous test approach.

request.body = .init(contentLength: nil, stream: streamCallback)

let delegate = NullResponseDelegate()
var maybeRequestBag: RequestBag<NullResponseDelegate>?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not use a ResponseAccumulator here instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I hadn't spotted that, thanks.

@rnro rnro requested a review from fabianfett January 28, 2025 16:47

@fabianfett fabianfett left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@rnro rnro merged commit f38c2fe into swift-server:main Jan 28, 2025
@rnro rnro deleted the write_timeout_crash branch January 28, 2025 17:50
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