Jens-G opened a new pull request, #3849:
URL: https://github.com/apache/thrift/pull/3849

   `bad_alloc_does_not_end_the_process` has failed intermittently on AppVeyor 
since it was added — 11 of 229 jobs, and 11 of 39 builds went red because of it 
— either crashing (ctest reports SEGFAULT) or hanging until the 300 s ctest 
timeout. There are two independent races, one behind each symptom, so both are 
fixed here. Test-only; no library change.
   
   ### The crash
   
   The fixture stopped the server and joined the serve thread, but never 
stopped the `ThreadManager`. Members are destroyed in reverse declaration 
order, so `server` goes before `threadManager_`, and `~TNonblockingServer` 
deletes every `TConnection` and the IO threads along with their notification 
pipe. A pool task still unwinding from the injected failure then reached 
`notifyIOThread()` — and `close()` — on the `TConnection` it holds by raw 
pointer.
   
   `~Fixture()` now stops the `ThreadManager` after joining the serve thread 
and before the members go. `ThreadManager::stop()` returns only once each 
worker has finished the task in its hands, so nothing is left running over the 
objects about to be destroyed.
   
   ### The hang
   
   `FailsFirstCallProcessor` failed whichever call reached `process()` first, 
tracked in a plain `bool` that both pool workers read and wrote. When the call 
from `canCommunicate()` won that race it consumed the failure and got no reply 
— and its client had no receive timeout, so the test body blocked until ctest 
killed it.
   
   The flag is now claimed with an atomic `exchange`, the processor signals a 
`Monitor` as it claims it, and the test waits for that signal before opening 
the second connection, so the failure is always taken by the call meant to 
receive it. The `exchange` matters on its own: read-then-write let two calls 
each see the flag unset and both throw, which is what the Windows logs showed 
as a doubled `bad_alloc` line.
   
   `canCommunicate()` also gets a 10 s receive timeout. Every call it makes is 
a localhost round-trip of a few bytes, so this only bounds the failure mode: a 
regression now reports in seconds with the transport exception instead of as an 
opaque 300 s timeout.
   
   ### Verification
   
   Rather than waiting on a race that is roughly 5 % on AppVeyor and 2 in 1200 
here, each schedule was forced:
   
   - **The hang** — delaying the first arrival at `process()` so the second 
connection claims the failure reproduces it exactly. Without the wait the case 
fails in 10 s with `THRIFT_EAGAIN (timed out)`; with it, the case passes.
   - **The crash** — holding the failing task in flight past the end of the 
test body puts ASan straight on the use-after-free once the `ThreadManager` 
stop is removed:
   
     ```
     ERROR: AddressSanitizer: heap-use-after-free ... READ of size 8
       #0 TConnection::notifyIOThread()  TNonblockingServer.cpp:302
       #1 TConnection::Task::run()       TNonblockingServer.cpp:376
     freed by thread T0 here:
       ... ~TNonblockingServer()         TNonblockingServer.cpp:964
       ... Fixture::~Fixture()
     ```
   
     With the stop in place ASan reports nothing.
   
   Then 5 clean runs of the full suite, and 200 runs of this case under ASan 
with eight in parallel.
   
   ### Not touched
   
   `allocation_failure_on_the_io_thread_does_not_end_the_process` fails under 
ASan because its `RLIMIT_AS` child collides with ASan's own allocator. That 
reproduces identically on unmodified master and is unrelated to this change.
   
   ---
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
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