Skip to content

dasHV↔libhv cross-thread seams surfaced by tsan-instrumented libhv #3428

Description

@borisbat

Building libhv with -fsanitize=thread on the tsan CI lane (follow-up to the test_writer_connected flake fix) lets TSAN see libhv's synchronization for the first time. Beyond the fixed gmtime race, it surfaces three real cross-thread seams, currently silenced by narrow entries in .github/tsan_suppressions.txt so the lane stays green. Each is pre-existing behavior, not a regression.

1. Keep-alive handler reuse vs deferred (HTTP_STATUS_UNFINISHED) responses

WebServer_Adapter::makeCtxHandler returns HTTP_STATUS_UNFINISHED and fills ctx->response later on the tick thread. libhv's per-connection HttpHandler reuses its request/response objects across keep-alive messages, so the loop thread can parse the next pipelined message (HttpHandler::onMessageComplete reads handler/response state) while the tick thread still writes the previous response (status_code writes in makeCtxHandler / das_resp_redirect).

Observed as 16 reports (14× makeCtxHandler write vs onMessageComplete read, 2× das_resp_redirect). With a request→response ping-pong client this window doesn't open; a pipelining client can genuinely corrupt a response. Suppressed via race:HttpHandler::onMessageComplete.

Fix directions: disable keep-alive pipelining readahead for deferred routes, clone req/resp for the deferred path, or park further reads on the connection until the deferred response is sent.

2. Cross-thread WebSocket sends

das_wss_send / das_wsc_send call hv::WebSocketChannel::send → hio_write from das threads while the channel's loop runs. libhv mutex-guards the write queue (the senders hold M0/M1 in the reports) but hio_write4 reads io state fields outside the guard. 4 reports. Suppressed via race:hio_write4.

Fix direction: marshal ws sends through the channel's loop (runInLoop), same as the writer ops now do.

3. hv::WebSocketClient open/close callback bookkeeping

Client onopen callback state vs WebSocketClient::close() from the das side. 9 reports. Suppressed via race:hv::WebSocketClient::close.

Benign-by-design libhv idioms (also suppressed, no action planned)

  • hloop_stop writes plain status/wakeup flags read by hloop_run / hloop_process_events (24 reports) — upstream idiom, worst case one extra loop iteration.
  • hio_is_opened polled cross-thread by das_writer_is_connected — a momentarily stale answer is the API's contract.

Repro: WSL Ubuntu2404-CI rig, tsan Release build, bin/test_aot -use-aot dastest/dastest.das -- --use-aot --failures-only --timeout 900 --test tests/dasHV with the suppressions file removed.

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