Repository navigation
[Fix] Add ResponseWriteTimeout so a client that does not read its response cannot hold a connection (#66) - #85
Merged
Conversation
…ponse cannot hold a connection (#66)
|
1 of 2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #66
Problem
Writing a response had no timeout. The host's pipe is unbuffered, so a write only completes when the client reads it. A client that sent a request and never read the response held its connection task, pipe instance,
MaxConcurrentRequestsslot andMaxConcurrentConnectionsslot until the host stopped.Changes
ArgusPipeHostOptions.ResponseWriteTimeout(default 30 seconds,Timeout.InfiniteTimeSpandisables it): the maximum time the host may take to send a complete response.WriteResponseAsynchelper. It links the timeout to the shutdown drain token; on expiry it logs the warning "The response was not sent within ...; closing the connection." and the connection is closed.ArgusChunkedWriteStream,ArgusChunkedEncoding): a compression stream writes its last block when it is disposed, without a cancellation token. On the unbuffered pipe that write could not be cancelled, so a client that read the start of a compressed streamed response and then stopped was still held, and shutdown could not end it either.ArgusChunkedWriteStreamnow takes the operation's token and uses it for writes whose caller passes none. The same path is used byArgusClientfor compressed streamed requests.Test (Windows)job inCodeQuality.yml. Some of the new tests only make sense on Windows (see below), and nothing in CI exercised Windows named pipes. The existingBuildjob, with Sonar and coverage, is unchanged.Behavior to be aware of
The timeout is a budget for the whole response, like
RequestReadTimeout, and not an idle timeout. For a streamed response (BodyStream) it includes the time the handler's stream takes to produce the data. A host that streams large or long-lived responses, which had no limit before, is cut off after 30 seconds by default and should raise or disable the timeout. This is documented on the option and in the wiki, and belongs in the release notes.Tests
Cross-platform:
MaxConcurrentConnections = 1andMaxConcurrentRequests = 1another client is then served, so both slots are releasedBodyStream) responseTimeout.InfiniteTimeSpanand a generous timeout deliver the complete response to a slow readerStopAsynccancels a blocked write within the drain timeout, without a write-timeout warningArgusChunkedWriteStream: the constructor's token is used for writes without a token, and with gzip every write to the destination carries the tokenWindows only (
[Platform("Win")]): the unread 400, 406 and both 503 responses, and the compressed streamed response that is read partially. On Linux and macOS a pipe is a Unix domain socket that buffers a small response, so the write completes without a reader and there is nothing to time out. They run in the newTest (Windows)job.Issue check
ResponseWriteTimeoutoption, default 30 seconds,Timeout.InfiniteTimeSpanto disableWriteResponseAsync; one test per write (four of them Windows only)Not in this PR
Verification
dotnet build ArgusTransfer.sln: 0 warningsdotnet test ArgusTransfer.sln: 687 passed (Windows); the 23 timing-sensitive tests passed in five repeat runsdotnet format ArgusTransfer.sln --verify-no-changes: cleandotnet build ArgusTransfer.sln -warnaserror: succeededTest (Windows)job; both run on this PR.Wiki:
Server-Configuration(option table and "Connection Limits and I/O Timeouts") andTroubleshooting.🤖 Generated with Claude Code