maskit opened a new pull request, #13952:
URL: https://github.com/apache/trafficserver/pull/13952

   ## Why
   
   On kqueue platforms (macOS, FreeBSD), a plugin that pauses 
`TS_VCONN_OUTBOUND_START_HOOK` and calls `TSVConnReenable()` later wedges the 
net thread that owns the connection. The thread spins at full CPU inside 
`NetHandler::process_ready_list()`, every other connection on that thread 
stalls, and the paused connection never resumes. 
`test_tls_hook_combination[outbound-delayed-start]`, whose test plugin 
reenables two seconds after the hook, hangs on macOS for this reason.
   
   The loop:
   
   1. While the hook is pending, `sslClientHandShakeEvent()` returns 
`SSL_WAIT_FOR_HOOK`. In `SSLNetVConnection::_beReadyToTransferData()` that 
falls into the final `else`, which calls `writeReschedule()`.
   2. On kqueue, `write_reschedule()` re-registers the write filter with 
`EV_ADD`. The socket is writable, so the next poll reports it again and sets 
`write.triggered`.
   3. From then on, each pass through `_beReadyToTransferData()` reschedules 
with `write.triggered` and `write.enabled` both set. That puts the VC straight 
back on the `write_ready_list` that `process_ready_list()` is draining, so the 
loop never ends.
   4. The thread never returns to its event loop, so its scheduled events never 
run. A `TSVConnReenable()` from another thread can't help either: it fails to 
take the NetHandler mutex the spinning thread holds, and hands the reenable to 
that same thread.
   
   On epoll, `EventIO::refresh()` does nothing and edge-triggered polling 
doesn't report the socket again, so `write.triggered` stays cleared and the 
handshake waits for the reenable. That is why Linux CI does not show the hang.
   
   ## Change
   
   When `_beReadyToTransferData()` gets `SSL_WAIT_FOR_HOOK` and a hook is 
actually pending (`is_invoked_state()`), skip the reschedule. `reenable()` 
resumes the handshake: after the last outbound start hook it re-arms the write 
side itself. `net_read_io()` already skips rescheduling for `SSL_WAIT_FOR_HOOK`.
   
   The `is_invoked_state()` check is needed because `SSL_WAIT_FOR_HOOK` also 
covers two cases that still rely on the reschedule:
   
   - The outbound PROXY protocol header write returns it for a partial write or 
an unconnected or unwritable socket, before any hook runs.
   - When a plugin reenables inside the hook, the nested `reenable()` has 
already moved the state back out of the invoked state and re-queued the VC, but 
`sslClientHandShakeEvent()` still returns `SSL_WAIT_FOR_HOOK`.
   
   `SSL_WAIT_FOR_ASYNC` is left unchanged.
   
   ## Validation
   
   On macOS with a BoringSSL build:
   
   - `test_tls_hook_combination[outbound-delayed-start]` passes; it hung on 
every run before this change.
   - All 18 `test_tls_hooks.py` cases, `test_tls_hooks_cert_resume`, 
`test_tls_hooks_client_verify`, `test_tls_hooks_verify`, and all 8 
`proxy_protocol_out` cases (including the https-origin ones that go through the 
PROXY protocol header path) pass serially.
   - The TLS, SSL and PROXY protocol urtests pass with 8 workers, apart from 
`test_tls_partial_blind_tunnel`, which fails the same way without this change 
and is a separate problem.
   
   The existing test reproduces the hang only on kqueue platforms, so Linux CI 
does not cover it. The changed path does not depend on the TLS library, but an 
OpenSSL build was not tested.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to