Skip to content

Long Test TSAN failures: JWT metric race and dropped FD suppressions #8256

Description

Describe the bug

The scheduled Long Test run 33573669385 on main at cd60587be4032532e7ce70d2546a15f34f83d765 failed its TSAN job for two independent reasons:

  1. reconfiguration_test_suite found a real data race in the JWT refresh metrics. Two CCF task workers concurrently update the same plain counters in NodeEndpoints::handle_event_request_completed().
  2. e2e_logging reported the known closedir/epoll_ctl file-descriptor reuse race which is already listed in tsan_env_suppressions. This test opts into DETECT_DEADLOCKS, and that CMake path replaces TSAN_OPTIONS without retaining the suppression file.

The same workflow also failed Long Shuffled/LTS/Snmalloc, but that failure is already tracked by #8248 and should not be duplicated here.

To Reproduce

Run the TSAN configuration of the Long Test workflow at the commit above.

  • Failing TSAN job and complete logs

  • The first failure is in reconfiguration_test_suite. TSAN reports concurrent writes at node_frontend.h:424 and then at line 432. Both stacks are:

    NodeEndpoints::handle_event_request_completed
    RpcFrontend::process_command
    JwtKeyAutoRefresh::send_refresh_jwt_keys
    JwtKeyAutoRefresh::handle_jwt_jwks_response
    BasicTask::do_task_implementation
    Enclave::run_worker
    

    The conflicting workers are T3 and T4.

  • The second failure is in e2e_logging. TSAN reports:

    Write: closedir
      std::filesystem::directory_iterator::~directory_iterator
      asynchost::get_file_name_with_idx
      Ledger::get_file_from_cache
      Ledger::on_ledger_get_async
    
    Previous read: epoll_ctl
      close_ptr<TCPImpl>::~close_ptr
      RPCConnectionsImpl::close
    

    TSAN associates both operations with file descriptor 25.

The closedir signature is recurring:

Expected behavior

JWT refresh metric updates and reads should be synchronised and provide a coherent snapshot.

Tests using DETECT_DEADLOCKS should continue to apply unrelated data-race suppressions, so the known closedir/epoll_ctl report does not terminate the test. TSAN should fail only for unsuppressed, actionable findings.

Environment information

  • Repository revision: cd60587be4032532e7ce70d2546a15f34f83d765
  • Workflow: Long Test, scheduled on main, 2026-09-02
  • Job: Debug TSAN
  • Runner SKU: Standard_D16ads_v6
  • Stacks use libstdc++ 13.2.0 and libuv

Additional context

Root cause 1: unsynchronised JWT metrics

JWTRefreshMetrics jwt_refresh_metrics is shared mutable state. handle_event_request_completed() increments attempts, failures, and successes without a lock or atomics. Curl completion callbacks can run through independent BasicTasks on multiple enclave workers, so the endpoint completion hook is not single-threaded.

There is a second unsynchronised access: GET /jwt_keys/refresh/metrics copies the same struct while updates may be in flight.

A fix must protect both writers and the reader. A mutex around counter updates plus copying a snapshot for the GET endpoint would preserve coherence across the three counters. If atomics are used instead, serialisation must explicitly load them and define whether a cross-counter coherent snapshot is required.

Root cause 2: the known FD report loses its suppression

The repository already documents and suppresses this exact report:

tsan_env_suppressions:

race:closedir
race:epoll_ctl

Normal TSAN tests receive:

TSAN_OPTIONS=suppressions=<repo>/tsan_env_suppressions

However, add_san_test_properties() makes DETECT_DEADLOCKS mutually exclusive with the suppression file:

TSAN_OPTIONS=detect_deadlocks=1:halt_on_error=1:second_deadlock_stack=1

e2e_logging is registered with DETECT_DEADLOCKS, so the known FD-reuse signature is exposed and halt_on_error=1 terminates the node during an asynchronous historical-ledger read.

The split originally avoided deadlock:*/store.h and deadlock:*/untyped_map.h suppressions. Those deadlock suppressions were removed by #8154; the file now contains only the independent closedir and epoll_ctl race suppressions. The configuration can therefore retain suppressions=... while enabling deadlock detection, or use separate race/deadlock suppression files if future entries require different treatment.

Pending concurrency PRs

None of the current concurrency fixes covers these accesses:

The shuffled ledger failure from the original run reproduced independently on #8242 and is tracked in #8248.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions