Conversation
evhtp stops reading from a connection while a request is paused, so a client disconnect was only noticed once a response was written, after the inference had already run to completion. Watch the socket with a non-consuming MSG_PEEK read while the request is in flight and cancel the Triton request when the client goes away.
This branch has not been deployed
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.
What does the PR do?
Cancels an in-flight HTTP inference request when the client disconnects, instead of letting it run to completion.
InferRequestClasspauses the evhtp request while the inference runs, andevhtp_connection_pause()disablesEV_READon the connection's bufferevent. Nothing reads the socket during that time, so the peer's FIN/RST is not seen andRequestFiniHook(the only place the HTTP frontend callsTRITONSERVER_InferenceRequestCancel) runs only after the response has been written.This PR registers a small
EV_READ | EV_PERSISTlibevent watcher on the connection fd for the lifetime of the request, on the evhtp thread that owns the connection. The callback does a non-consumingrecv(..., MSG_PEEK):0or a socket error: cancel the Triton request and drop the watcher.EAGAIN/EWOULDBLOCK/EINTR: keep watching.The watcher is freed in the request fini hook and in the destructor. evhtp frees the request (and runs the fini hook) before it frees the bufferevent that closes the fd. No libevhtp change is needed. Since the logic lives in
InferRequestClass, it covers/infer,/generate,/generate_streambefore the first chunk, and the SageMaker / Vertex AI request classes.Checklist
Agreement (CLA is being submitted)
<commit_type>: <Title>pre-commit install, pre-commit run --all)Commit Type:
Related PRs:
None.
Where should the reviewer start?
DisconnectWatchCallbackandStartDisconnectWatch/StopDisconnectWatchinsrc/http_server.cc, and where they are called from (theInferRequestClassconstructor,RequestFiniHook, the destructor insrc/http_server.h).Test plan:
Added
qa/L0_request_cancellation/http_cancellation_test.pyand a section in that suite'stest.sh, using the existingexecute_cancelPython model (it pollsis_cancelled()every second):test_http_infer_client_disconnect: close the socket 2 s into a 10 s/inferrequest and expect the model to log the cancellation.test_http_generate_client_disconnect: same through/generate.test_http_infer_client_connected: two requests on one keep-alive connection complete normally and nothing is cancelled.I built the
tritonserverexecutable frommainand from this branch and ran both against the 26.08 container'slibtritonserver.soand backends:maintest_http_infer_client_disconnecttest_http_generate_client_disconnecttest_http_infer_client_connectedServer log with the fix:
I also ran an ad-hoc stress script against the patched build (not part of the PR): 500 connections closed at random points around the response, half with RST; 32 threads x 25 keep-alive requests; two pipelined requests on one socket (neither cancelled, both answered). No crash, all responses correct, clean server exit.
Caveats:
shutdown(SHUT_WR)) after sending the request and then waits for the response is treated as disconnected. This is rare for HTTP/1.1 clients and matches nginx's default (proxy_ignore_client_abort off). I can put the detection behind a server option if you'd prefer to keep the old behavior available.build.pybuild, the rest of the L0 suites, or an ASAN build, and the_WIN32branch has not been compiled. Opening as a draft so CI can cover that.Background
Long-running requests (LLMs in particular) keep occupying the GPU after an HTTP client times out or is interrupted. gRPC already propagates cancellation; HTTP did not for non-streaming requests. Root cause analysis is in #7077 (comment).
Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)