# ThreadSanitizer suppressions for NeoGraph's tsan-test CI gate.
#
# These suppress races inside libraries we depend on but don't ship —
# their internal synchronization is correct (e.g. asio's reactive
# socket service uses internal mutexes + lock-free atomics that
# happen to confuse TSan's happens-before analysis). NeoGraph's own
# code never appears here; if you find yourself adding a NeoGraph
# symbol, that's a real race — fix it.
#
# Format: `race:<symbol_substring>`.

# asio's reactive socket service: epoll-based reactor uses lockless
# operation lists with explicit memory ordering. TSan doesn't model
# the FD-table happens-before edges across kernel boundaries, so it
# flags the close()-vs-pending-read shape that asio handles by design.
# Confirmed benign by asio upstream (chriskohlhoff/asio#1022).
race:asio::detail::reactive_socket_service_base::close
race:asio::detail::reactive_socket_service_base::do_start_op
race:asio::detail::reactive_socket_service_base::start_accept_op
race:asio::detail::socket_ops::set_internal_non_blocking

# asio epoll/kqueue reactor — same story as above. The reactor's
# internal queues use atomic compare-exchange for op queueing; TSan
# misses the happens-before from the kernel's epoll_wait return.
race:asio::detail::epoll_reactor
race:asio::detail::reactor_op_queue

# yyjson: parser uses SIMD-aware byte scanning that TSan reports as
# a benign race (the read window can overlap a write window only when
# the input buffer is shared across threads, which our usage doesn't
# do — but TSan's static analysis can't prove it).
race:yyjson_read

# OpenSSL: thread-local error queues use a global atomic counter. The
# write-write race TSan reports is on the lazy init path, mediated by
# CRYPTO_ONCE. Functionally correct.
race:CRYPTO_THREAD_run_once
race:OPENSSL_init_crypto

# Asio's shared any_executor reference-count implementation uses an atomic
# decrement followed by delete on the final owner. TSan reports the atomic
# decrement in a concurrent non-final owner as racing with that delete even
# though the reference-count modification order makes the delete occur only
# after the final decrement. This is dependency code, not a NeoGraph access.
race:asio::execution::detail::shared_target_executor

# std::exception_ptr's reference count. libstdc++.so is not built with
# ThreadSanitizer, so TSan cannot see the acquire/release atomics on an
# exception object's reference count. A fan-out step hands each worker's
# exception to the step driver: the driver reads it (exit_of reads a
# NodeInterrupt's payload) and drops its reference, and the worker releases the
# last one afterwards. The count orders the worker's release after the driver's
# read, but TSan sees no ordering and reports the destruction (the payload's
# yyjson pools, then the exception object, all freed inside
# exception_ptr::_M_release) as a race with that read. The three reports of
# one such hand-off are one event.
#
# Checked, not assumed. The same reports come from a NodeInterrupt handed
# between two threads this way, and from a bare std::exception_ptr. With the
# count made visible to TSan (a test-only stand-in for _M_addref and
# _M_release using instrumented atomics) they are gone, and a read made after
# the reader dropped its reference is still reported. The payload's yyjson
# document is freed by the last std::shared_ptr release, which TSan does see,
# so no other thread still holds it.
#
# Limit: the frame matches in either stack, so a race whose other access is
# made while the last exception_ptr reference is destroyed is not reported
# either. The frame is in libstdc++, not in NeoGraph or yyjson.
race:std::__exception_ptr::exception_ptr::_M_release
