Skip to content

Add TSAN fiber-switching support, sanitizer/valgrind bazel configs, and CI#33

Merged
dallison merged 1 commit into
mainfrom
tsan-and-ci
May 3, 2026
Merged

Add TSAN fiber-switching support, sanitizer/valgrind bazel configs, and CI#33
dallison merged 1 commit into
mainfrom
tsan-and-ci

Conversation

@dallison

@dallison dallison commented May 3, 2026

Copy link
Copy Markdown
Owner

Summary

Re-opens the work from the (now-reverted) PR #32 with clean commit messages
(no Co-authored-by: Cursor <[email protected]> trailers, since BCR
rejects those). Same content, single squashable commit on top of
081378c.

  • Teach the coroutine library to cooperate with ThreadSanitizer via its fiber
    API (__tsan_create_fiber / __tsan_switch_to_fiber / ...), in the same
    spirit as the existing AddressSanitizer integration. Every cooperative
    context switch now tells TSan which fiber it is leaving and which it is
    entering, so yields between the scheduler and its coroutines no longer look
    like data races.
  • Add three reusable Bazel --config= profiles in .bazelrc for running the
    test suite under sanitizers / Memcheck:
    • --config=asan-fsanitize=address with frame pointers and debug info.
    • --config=tsan-fsanitize=thread with frame pointers and debug info.
    • --config=valgrind-O1 -g, runs each test via --run_under with
      valgrind --error-exitcode=1 --leak-check=full --track-origins=yes ....
  • Add a GitHub Actions CI workflow (.github/workflows/ci.yml) modeled on
    the one used by dallison/subspace,
    extended to exercise every config above on every push and pull request.

Implementation notes

TSan integration

  • detect_sanitizers.h gains CO_THREAD_SANITIZER (auto-detected from
    __has_feature(thread_sanitizer) for clang and __SANITIZE_THREAD__ for
    gcc) and a matching CO_DISABLE_THREAD_SANITIZER attribute.
  • coroutine.h forward-declares the five TSan fiber entry points we use and
    adds a tsan_fiber_ member to both Coroutine and CoroutineScheduler.
  • coroutine.cc:
    • Adds a CO_TSAN_SWITCH_TO_FIBER(fiber) helper that compiles to
      __tsan_switch_to_fiber(fiber, 0) under TSan and a no-op otherwise.
    • Wires the helper into all three SWAPCONTEXT macro variants
      (setjmp / ucontext / custom-context) and into every SETCONTEXT(resume_),
      SETCONTEXT(exit_), and the tail of InvokeFunction.
    • Creates and names a per-coroutine fiber in the constructor and destroys
      it in the destructor.
    • Captures the scheduler thread's fiber lazily on the first call to Run(),
      since the scheduler may have been constructed on a different thread.
    • Annotates Coroutine::Resume with CO_DISABLE_THREAD_SANITIZER (it
      contains hand-written stack-switching assembly that TSan should not try
      to instrument) alongside the existing CO_DISABLE_ADDRESS_SANITIZER.

CI workflow

.github/workflows/ci.yml defines three jobs, each using
bazel-contrib/[email protected] with bazelisk / disk / repository caching:

Job Runners What it runs
Build & test (matrix) ubuntu-latest, ubuntu-24.04-arm, macos-latest (with --config=apple_silicon) bazel build //... then bazel test //...
sanitizers (matrix: asan, tsan) ubuntu-latest bazel test --config=$config //...
valgrind ubuntu-latest Installs valgrind via apt, then bazel test --config=valgrind //...

Failed runs upload bazel-testlogs/ as artifacts for post-mortem.

Test plan

  • bazel build //co:co //co:cotest //co:costress //co:coroutines_test — passes locally.
  • bazel test --config=asan //co:coroutines_test //co:test_cpp20 — both pass locally.
  • bazel test --config=tsan //co:coroutines_test //co:test_cpp20 — both pass locally.
  • bazel build --config=valgrind //co:coroutines_test //co:test_cpp20 — builds clean locally (running under Valgrind requires Linux).
  • CI on the previous (reverted) iteration of this work passed all six jobs (Build & test (macos-latest / ubuntu-24.04-arm / ubuntu-latest), asan, tsan, valgrind); CI on this PR will reproduce the run.

Notes for review

  • This branch contains a single commit; squash-merge or merge as you prefer.
    Either way the resulting commit message will be clean (no Cursor trailer).
  • After merge, please cut a new release / version bump separately rather than
    relying on the dangling 3.3.1 / 3.3.2 tags from the previous attempt.

…nd CI

Following the same pattern as the existing AddressSanitizer integration,
the coroutine library now cooperates with ThreadSanitizer via its fiber
API (__tsan_create_fiber / __tsan_switch_to_fiber / ...) on every
coroutine context switch, eliminating spurious data-race reports across
cooperative yields between the scheduler and its coroutines.

* detect_sanitizers.h: add CO_THREAD_SANITIZER and
  CO_DISABLE_THREAD_SANITIZER, mirroring the ASAN macros.
* coroutine.h: forward-declare the TSan fiber API and add a
  per-coroutine tsan_fiber_ plus a per-scheduler tsan_fiber_.
* coroutine.cc: switch fibers on every Resume/Yield/Exit and at the
  end of InvokeFunction; create and name a fiber in the Coroutine
  ctor, destroy it in the dtor, and capture the scheduler thread's
  fiber lazily in Run().

Also adds .bazelrc configurations for asan, tsan, and valgrind so the
test suite can be exercised under each, e.g.

    bazel test --config=asan     //co:coroutines_test //co:test_cpp20
    bazel test --config=tsan     //co:coroutines_test //co:test_cpp20
    bazel test --config=valgrind //co:coroutines_test //co:test_cpp20

And adds a GitHub Actions CI workflow (.github/workflows/ci.yml)
modeled on the one used by dallison/subspace, extended to exercise
every config above on every push and pull request.
@dallison dallison merged commit ebc77a0 into main May 3, 2026
12 checks passed
@dallison dallison deleted the tsan-and-ci branch May 3, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant