Test Details Overview

Fix use-after-free in rpc test response helper (#5166)

* Fix use-after-free in rpc test response helper

`test_response` chained async_connect -> async_write -> async_read with handlers
capturing a raw `this`, while callers held it by value or unique_ptr. Nothing tied
the object's lifetime to the operations it had started, so dropping the caller's
handle mid-flight left queued handlers pointing at freed memory. A failing
ASSERT_TIMELY inside `wait_response_impl` is exactly that path, since it expands
to a bare `return`.

The macOS TSAN CI job hits this as a SEGV during `~system()`: the RPC request times
out, the stack allocated `test_response` is destroyed, and the pending write
completion then starts a read on the destroyed socket while the teardown poll loop
drains the io_context.

Make `test_response` shared and give every handler a lifetime guard. Construction
goes through `prepare`/`send`, so stack allocating one - the shape that caused the
bug - no longer compiles. `request` becomes a value copy, as the object can now
legitimately outlive the caller's ptree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Join worker threads in rpc simultaneous_calls

The worker threads were detached while capturing `test_responses`, `promise` and
`count` by reference. A failing assertion returns from the test body and destroys
those stack objects, so any assertion firing while the threads are still running
left them writing into dead memory - the same defect as the response helper, one
scope up. Injecting an early assertion failure and running under ASAN reports
`stack-use-after-scope` in the `--count` decrement.

`std::jthread` would cover this, but libc++ on macOS does not provide it, so add a
`join_guard` next to the other test guards and join explicitly. It is declared after
the objects the threads capture, so it is destroyed first and joins before they go
away.

Joining right after the spawn loop instead would be simpler, but this test exists to
exercise threading issues under TSAN, which relies on the io_context being pumped by
the `ASSERT_TIMELY` below while the workers are still calling `run`. The guard keeps
that overlap and only changes the failure path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

__________

Type: commit

Hash: a1ccec51a113f95e8adedff9595c7acc6e11364b

Created: yesterday

__________

gr0v1ty/nano-node:a1ccec51a113f95e8adedff9595c7acc6e11364b

Test Results

Test Case Status Duration Median Deviation Log Graph