Skip to content

Connections not being closed on client abort when allow_half_open is disabled #13549

Description

@GUI

Summary

When Traffic Server is proxying a request to an upstream server and the client aborts the request before the upstream server has responded at all, Traffic Server appears to still try and complete the request, rather than cancelling the request and relaying this cancellation to the upstream server.

Under Traffic Server 9.2 and 10.0, this behavior can seemingly be controlled by proxy.config.http.allow_half_open, but in Traffic Server 10.1.4 and 10.2.0-rc1, I can't find any way to have Traffic Server abort these canceled client requests.

In situations where the upstream server is very slow to respond and clients cancel many requests, Traffic Server still holds open the connections for any cancelled requests to the upstream server. In a worst-case scenario, this may eventually cause Traffic Server to hit its own proxy.config.net.max_requests_in or proxy.config.net.connections_throttle limits if it's completely occupied trying to fulfill all of the canceled requests. I realize there are some situations where you'd maybe want to try and let the upstream requests complete, but I'm trying to figure out why changing that allow_half_open setting doesn't seem to affect 10.1 and 10.2 in the same way as before.

Based on the docs, I was also wondering if proxy.config.http.background_fill_active_timeout or proxy.config.http.background_fill_completed_threshold could play a role, but regardless of settings, I can't get these requests canceled by the client to stop processing in Traffic Server 10.1 or 10.2.

So, am I missing some other setting to control this, or is this a bug in 10.1 and 10.2?

Possibly related PRs

I'm proxying over HTTP 1.1, if that's relevant. I dug up several semi-recent PRs that seem to be touching this code, but they all seem maybe more related to HTTP 2 behavior, so I'm wondering if maybe this is no longer working properly for HTTP 1.1 proxied requests (but I haven't verified things with anything besides HTTP 1.1):

Logs and related code

Debug logging indicates that when the client cancels the request, Traffic Server is hitting the state_watch_for_client_abort handler with a VC_EVENT_EOS event:

[ET_NET 0] DIAG: <HttpSM.cc:2795 (main_handler)> (http) [2] VC_EVENT_EOS/TS_EVENT_VCONN_EOS, 104
[ET_NET 0] DIAG: <HttpSM.cc:896 (state_watch_for_client_abort)> (http) [2] [state_watch_for_client_abort, VC_EVENT_EOS/TS_EVENT_VCONN_EOS]

With allow_half_open disabled, then in 10.1.4 or 10.2.0-rc1, it's landing in this else if block:

} else if (t_state.txn_conf->cache_http &&
(server_entry != nullptr && server_entry->vc_read_handler == &HttpSM::state_read_server_response_header)) {
// if HttpSM is waiting response header from origin server, keep it for a while to run background fetch
_ua.get_txn()->do_io_shutdown(IO_SHUTDOWN_READWRITE);

However, if I remove that else if block entirely (so it falls into the else logic), then the request is canceled as I was originally expecting. It seems like that else if block was intentionally added by #12529 to fix a very similar sounding issue, though. I'm not familiar enough with the codebase or what that PR was originally fixing to know what the proper solution is here, but it seems like some of these more recent changes to state_watch_for_client_abort could be playing a role.

Reproduction

Here's a little repo (client-abort branch) showing how the cancellation behavior doesn't seem to be working as I'd expect in Traffic Server 10.1 and 10.2: https://github.com/GUI/trafficserver-debugging/tree/client-aborts

This reproduction essentially consists of an nginx upstream server that sleeps for 10 seconds and then responds. Traffic Server is proxying to this upstream with the allow_half_open option disabled.

With Traffic Server 9.2, 10.0, or nginx itself proxying, you can see the upstream connection is aborted after the client cancels the request. However, in Traffic Server 10.1.4 or 10.2.0-rc1, the upstream connection continues until it is complete (which is not what I want with allow_half_open disabled).

Direct upstream request with timeout

curl -w 'time_total: %{time_total}s\n' --max-time 2 'http://localhost:8081/'
# curl: (28) Operation timed out after 2005 milliseconds with 0 bytes received
# time_total: 2.004165s

Server logs show nginx upstream canceling request immediately after client hangs up (after 2 seconds):

nginx-1    | 192.168.65.1 - - [14/Aug/2026:04:07:06 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/8.7.1" request_time=2.004 msec=1786680426.901

Traffic Server 9.2/10.0 cancellation

curl -w 'time_total: %{time_total}s\n' --max-time 2 'http://localhost:10100/'
# curl: (28) Operation timed out after 2006 milliseconds with 0 bytes received
# time_total: 2.006394s

Server logs show both nginx upstream and Traffic Server closing connection after the 2 second client timeout.

nginx-1    | 192.168.224.2 - - [14/Aug/2026:04:08:50 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/8.7.1" request_time=1.998 msec=1786680530.387
ts-10-0-1  | 192.168.65.1 - - [14/Aug/2026:04:08:48 -0000] "GET http://nginx:8081/ http/1.1" 000 0 000 0 0 0 90 0 226 17 2 EMPTY INTR FIN ERR_CLIENT_ABORT ttms=2009 stms=-1

BUG SCENARIO: Traffic Server 10.1.4/10.2.0-rc1 not aborting

curl -w 'time_total: %{time_total}s\n' --max-time 2 'http://localhost:10102/'
# curl: (28) Operation timed out after 2002 milliseconds with 0 bytes received
# time_total: 2.002365s

Despite the client hanging up after 2 seconds, server logs show that both Traffic Server and the nginx upstream kept the connection open for the full 10 seconds it took for the upstream to respond (see request_time and ttms in log output):

nginx-1    | 192.168.224.4 - - [14/Aug/2026:04:11:18 +0000] "GET / HTTP/1.1" 200 29 "-" "curl/8.7.1" request_time=10.001 msec=1786680678.975
ts-10-2-1  | 192.168.65.1 - - [14/Aug/2026:04:11:08 -0000] "GET http://nginx:8081/ http/1.1" 200 0 200 0 0 0 90 177 226 148 10 DIRECT INTR FIN ERR_CLIENT_READ_ERROR ttms=10020 stms=10007

nginx proxy cancellation

Just for comparison, there's also an nginx proxy in front of the nginx upstream:

curl -w 'time_total:  %{time_total}s\n' --max-time 2 'http://localhost:10092/'
curl: (28) Operation timed out after 2003 milliseconds with 0 bytes received
time_total:  2.003557s

You can see this nginx proxy behaves the same as Traffic Server 9.2 or 10.0, with both requests being canceled after the 2 second client timeout:

nginx-1    | 192.168.65.1 - - [14/Aug/2026:04:13:02 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/8.7.1" request_time=2.007 msec=1786680782.467
nginx-1    | 127.0.0.1 - - [14/Aug/2026:04:13:02 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/8.7.1" request_time=2.005 msec=1786680782.467

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions