Skip to content

WIP: TSAN record thread fix (and restore threads on restart)#8

Open
gc00 wants to merge 23 commits into
mainfrom
tsan-record-thread-fix
Open

WIP: TSAN record thread fix (and restore threads on restart)#8
gc00 wants to merge 23 commits into
mainfrom
tsan-record-thread-fix

Conversation

@gc00

@gc00 gc00 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

This is still a WIP. But with Claude (and after about 10 hours), DeepDebug can now checkpoint and restart. Next, we need it to handle multithreaded_fork in the presence of TSAN. Hopefully, the solution will be similar to the solution during DMTCP restart (restore threads).

Summary by CodeRabbit

  • New Features

    • Improved ThreadSanitizer compatibility during checkpoint/restart, including better handling of multithreaded fork, thread exit, and thread recreation.
    • Added new public helper APIs for TSAN-safe allocation and signal-block detection.
  • Bug Fixes

    • Fixed restart hangs and crashes related to sanitizer-internal threads and recreated threads.
    • Improved thread ID translation and join behavior for more reliable multithreaded execution.
  • Documentation

    • Added design and implementation plans for the ThreadSanitizer port work.

gc00 and others added 5 commits June 30, 2026 11:56
The TSAN-supporting DMTCP branch (tsan-phased-init) bumped the plugin API
from v3 to v4, an ABI change (DmtcpPluginDescriptor_t / DmtcpUniqueProcessId,
new DmtcpCkptHeader etc.). DMTCP refused to load libmcmini.so:

  ASSERT pluginmanager.cpp:228: incompatible DMTCP plugin API version:
  plugin_api=3 expected=4

Sync the vendored include/dmtcp.h to DMTCP's v4 header (correct version
string and descriptor ABI), and carry forward the only McMini-specific
additions -- the mcmini_virtual_pid / mcmini_real_pid macros -- updated to
the v4 function names (dmtcp_{real_to_virtual,virtual_to_real}_pid became
dmtcp_pid_{real_to_virtual,virtual_to_real}). Also update the two direct
callers in multithreaded_fork.c. The unused dmtcp_restore_buf_* decls are
dropped (not referenced by libmcmini, and gone from v4).

Verified: libmcmini.so builds against v4 and DMTCP now loads it without the
version assert (mcmini record mode reaches RECORD-phase execution under
dmtcp_launch).

Known follow-up (out of scope here): checkpointing a -fsanitize=thread
target under deep-debug still crashes in RECORD mode with the TSan null-
ThreadState SEGV, because libmcmini creates threads via
libdmtcp_pthread_create (dlopen'd directly), bypassing libtsan's
pthread_create interceptor -- the same class of issue the classic-mode
RTLD_NEXT fix addresses, but on the DMTCP record path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a -fsanitize=thread target runs under DMTCP, dmtcp_launch prepends
libtsan ahead of libmcmini, so a new thread's entry order is
  dmtcp thread_start -> libmcmini's mc_thread_routine_wrapper -> libtsan
  trampoline -> user routine.
libmcmini's wrapper thus runs BEFORE libtsan has registered the thread.
Its prologue then called libc functions that libtsan intercepts (malloc,
and the raw pthread_rwlock_* in insert_pthread_map), and those interceptors
dereference the unregistered thread's null ThreadState -> SEGV / TSan
"sanitizer_thread_registry.cpp:348" CHECK. (Full analysis in
TSAN-McMini-DMTCP.txt.)

Make the prologue free of TSan-intercepted libc calls:
  - Add mc_ts_alloc (mem.c/mem.h): a bump allocator over a static BSS arena.
    No libc call and no syscall on the fast path (only an atomic bump), so
    it never enters a TSan interceptor. Fail path uses raw syscalls only.
    Never frees (the pthread_map / rec_list nodes it backs are never freed).
  - insert_pthread_map / search_pthread_map: use libmcmini's libpthread_*
    handle wrappers (which bypass libtsan) instead of the raw pthread_rwlock_*
    symbols, and mc_ts_alloc instead of malloc. Also fixes a pre-existing
    missing-unlock bug in search_pthread_map's found path.
  - add_rec_entry_record_mode_ts (record.c): mc_ts_alloc-backed variant of
    add_rec_entry_record_mode; the prologue's THREAD-record insert uses it.

Verified: mcmini -i 3 on ~/dmtcp.git/test/tsan_target (DMTCP tsan-phased-init,
plugin API v4) no longer crashes in the thread-creation prologue; both worker
threads now run under RECORD mode.

Scope: this fixes the prologue only. A separate TSan-interception site remains
downstream -- mc_pthread_join calls pthread_timedjoin_np directly (no bypass
handle), tripping libtsan's ConsumeThreadUserId CHECK -- to be addressed next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mc_pthread_join's RECORD loop called pthread_timedjoin_np directly. Under a
TSAN target, that symbol resolves to libtsan's interceptor, whose
ConsumeThreadUserId trips a thread-registry CHECK
(sanitizer_thread_registry.cpp:348) and aborts.

Add a libpthread_timedjoin_np handle (dlsym'd from libpthread, like the
mutex/cond/sem wrappers) that bypasses libtsan, and call it from
mc_pthread_join's RECORD loop instead of the raw symbol.

With this, the three-fix stack achieves END-TO-END checkpointing of a
-fsanitize=thread target under deep-debug (mcmini record mode):
  1. 5be8500  DMTCP plugin API v3 -> v4      (DMTCP loads libmcmini.so)
  2. 4bf2720  TSan-safe RECORD prologue      (worker creation doesn't crash
             before libtsan registers the thread)
  3. this     timed-join via bypass handle   (join loop doesn't trip the
             registry CHECK)

Verified: `mcmini -i 3 ~/dmtcp.git/test/tsan_target` (DMTCP tsan-phased-init)
now runs with no SEGV / no ThreadSanitizer errors and DMTCP produces a valid
checkpoint (ckpt_tsan_target_*.dmtcp, ~5 MB, matching the no-mcmini baseline).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
libmcmini's template thread was created via libdmtcp_pthread_create,
bypassing libtsan's pthread_create interceptor. When the target is built
with -fsanitize=thread, that left the template thread with no libtsan
ThreadState. On DMTCP restart, libtsan's setjmp/longjmp restore
(reached from threadlist.cpp:stopthisthread) dereferences the thread's
absent ThreadState and crashes with SIGSEGV during thread restore.

Create the template thread through the public pthread_create instead, so
libtsan's interceptor registers it and wraps its start routine. A new
thread-local flag mc_creating_internal_thread tells mc_pthread_create to
skip the user-thread/model-checking machinery and still route the actual
creation through DMTCP (libdmtcp_pthread_create), keeping the thread
DMTCP-known. When the target is not instrumented, the public
pthread_create resolves straight to mc_pthread_create and behavior is
unchanged.

With this, record -> checkpoint -> restart of a TSAN target survives
thread restore (verified: raw dmtcp_restart restores all threads with no
SIGSEGV, where it previously crashed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gc00 gc00 marked this pull request as draft July 2, 2026 01:38
gc00 and others added 18 commits July 4, 2026 22:28
Remove the throwaway SIGSEGV-backtrace handler and its install call
sites added during TSan-restart investigation, and restore the
DMTCP_EVENT_RESTART SIG_DFL reset for SIGSEGV. Keep the R2 (TSan fork
syscall hooks around _Fork()) and R4 (fresh TSan fiber for recreated
threads) prototypes as the starting point for the multithreaded_fork
TSan port described in PLAN.txt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eads from restart barrier

Documents the per-tid classification design for template_thread()'s
thread-count barrier: skip self, the checkpoint thread (via a new
read-only tid lookup), and TSan-internal threads (via a SigBlk probe
in a new src/lib/tsan_support.c). Ready for the writing-plans step.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bite-sized task plan for docs/superpowers/specs/2026-07-04-tsan-port-phase1-r1-design.md:
Task 1 adds thread_blocks_signal() in a new src/lib/tsan_support.c;
Task 2 adds a read-only get_tid_from_pthread_descriptor() and rewires
template_thread()'s restart barrier to per-tid classification; Task 3
is the environment-gated end-to-end DMTCP+TSan verification. All code
snippets were test-compiled in a scratch copy before committing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements a helper function to detect if a thread has a signal blocked
via its /proc/self/task/<tid>/status SigBlk mask. This will be consumed
by Task 2 to identify and skip ThreadSanitizer's internal background
thread during DMTCP checkpoint restart.

- Add include/mcmini/spy/checkpointing/tsan_support.h with function declaration
- Add src/lib/tsan_support.c with implementation
- Add test/tsan_support/test_thread_blocks_signal.c standalone unit test
- Wire new source file into CMakeLists.txt LIBMCMINI_C_SRC list

Test verified: compiles with -Wall -Werror, test passes (exit code 0).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
template_thread() previously counted expected restart-barrier posts by
taking /proc/self/task's thread count and subtracting a blanket 2 (for
itself and the checkpoint thread). Under TSan, libtsan spawns its own
background thread that blocks all signals at creation and never calls
into libmcmini's wrappers, so it never posts to dmtcp_restart_sem,
causing template_thread() to hang waiting for a post that never comes.

Replace the blanket subtraction with per-tid classification: skip the
template thread's own tid and the checkpoint thread's tid (read via
the new read-only get_tid_from_pthread_descriptor() sibling of
patchThreadDescriptor()), and skip any tid that blocks
SIG_MULTITHREADED_FORK (thread_blocks_signal(), from Task 1) as a
TSan-internal thread. A self-check re-verifies the pthread_t->tid
offset against pthread_self() before trusting it for the checkpoint
thread's descriptor, aborting via libc_abort() on mismatch.

Adds a standalone host-side test
(test/tsan_support/test_tid_from_descriptor_offset.c) proving the
read-only offset technique works for reading another thread's
descriptor, not just pthread_self().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…pt tid

The per-tid /proc/self/task walk skips self_tid (always reliable, from
SYS_gettid) and ckpt_tid (read from ckpt_pthread_descriptor via a raw
offset read, which could be stale or point at a since-changed offset).
If ckpt_tid doesn't match any live tid, the checkpoint thread's real
entry silently falls into the countable branch, corrupting
thread_count and hanging the subsequent barrier loop forever with no
diagnostic. Track how many times each tid was actually seen and abort
with a clear message if either isn't exactly 1, turning a silent hang
back into the exact stabilization bug this code was written to fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Documents that R2 and half of R4 are already committed from Phase 0's
keeper prototypes, scoping this phase down to R3 (__clone) and R4's
remaining half (forking-thread fiber switch). Also documents that
ThreadSanitizer itself works in this sandbox under `setarch -R`,
enabling a standalone test harness that mirrors dmtcp-callback.c's
actual getcontext-direct-call mechanism (not the vendor package's
signal-based one) without needing DMTCP.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bite-sized plan for docs/superpowers/specs/2026-07-04-tsan-port-phase2-r2-r3-r4-design.md:
Task 1 writes a standalone harness mirroring dmtcp-callback.c's actual
getcontext-direct-call mechanism and captures real RED (TSan CHECK
failed in ForkChildAfter) and GREEN evidence via a compile-time
MTF_BUGGY toggle; Task 2 applies the identical fix (__clone extern +
swap, forking-thread fiber switch) to production; Task 3 is the
environment-gated end-to-end DMTCP+TSan verification. Both the harness
and the production diff were test-compiled/run in a scratch copy
before committing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ber) under TSan

Adds test/tsan_support/test_fastpath_fork_clone_fiber.c, a self-contained
host-side test (compiled/run directly with gcc, not via CMake) that mirrors
dmtcp-callback.c's fast-path fork+clone+fiber resumption mechanism to prove
under real ThreadSanitizer that:

- R3 (using libc's __clone instead of the public, libtsan-intercepted
  clone()) and R4's forking-thread fiber switch are both required: with
  -DMTF_BUGGY (public clone(), no forker fiber switch) the recreated
  threads crash with "ThreadSanitizer: CHECK failed: tsan_rtl.cpp:253
  ((!thr->slot)) != (0)" inside ForkChildAfter, and the child process exits
  with code 66.
- The default (fixed) configuration passes cleanly: both parent and child
  report shared_counter=300000 (expected 300000), the child exits 0, and no
  CHECK failures occur across repeated runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ded_fork

restart_child_threads_fast() now calls libc's raw __clone() instead of the
public clone(), since libtsan's clone() interceptor treats every call as a
fork and corrupts its thread-slot state for the CLONE_THREAD clone used to
recreate pre-checkpoint threads. Also switch the forking thread itself onto
a fresh TSan fiber in fast_multithreaded_fork()'s child branch, since it
otherwise keeps its inherited (fork-copied) ThreadState whose shadow call
stack can overflow as the thread keeps running. Both fixes were already
proven against a standalone TSan repro harness in a prior commit; this
applies them to the production DMTCP restart path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rness comment

Final review found the harness's forking thread (main()) never does enough
post-fork instrumented work to trigger the shadow-call-stack overflow that
the forking-thread fiber switch (R4 remainder) guards against, so the
RED/GREEN evidence only actually falsifies/confirms R3 (__clone), not R4.
Rewrite the header comment to state this accurately; no code logic changes.
…witch

The fiber switch is guarded by #ifndef MTF_BUGGY, so it does not execute
in the buggy build -- only the default (fixed) one. Doesn't change any
of the comment's substantive conclusions (still not independently
validated by this harness), per final re-review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Static code-path analysis of mc_pthread_join resolves PLAN.txt's own
open question (Q1): the join CHECK-fail cannot reproduce here, since
no code path reachable by a recreated thread ever calls a real
libc/libpthread join (TARGET_BRANCH* is a pure mailbox handshake with
the mcmini scheduler). No join-side change needed.

pthread_exit is a real gap (not intercepted at all today) with a
second, previously-undocumented consequence found during this
investigation: McMini's existing THREAD_EXIT_TYPE machinery is only
triggered by a thread routine returning normally, never by an explicit
pthread_exit() call, for any thread. Designs a single mc_pthread_exit()
interceptor (mirroring mc_transparent_exit/abort's mode-dispatch) that
fixes both the TSan-safety gap and the model-checking gap together,
without needing the standalone package's retval-stash/fiber-switch
machinery -- McMini's own thread-exit never terminates the OS thread
at the kernel level, so that machinery would have nothing to protect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bite-sized plan for docs/superpowers/specs/2026-07-05-tsan-port-phase3-r5-design.md:
Task 1 proves the dlopen+dlsym forwarding technique standalone; Task 2
adds mc_pthread_exit() and wires it into interception.c/wrappers.c;
Task 3 is the environment-gated end-to-end DMTCP+TSan verification
with an explicit-pthread_exit() target. The full production diff was
test-applied and built clean in a scratch copy, and the standalone
forwarding test was run (PASS), before committing this plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…technique

This test demonstrates that pthread_exit can be safely resolved via dlopen+dlsym
and called from a worker thread, with the return value correctly delivered to
pthread_join(). This technique is used in the next task for production code.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Intercepts pthread_exit() and routes it through the existing
THREAD_EXIT_TYPE model-checking machinery (mc_exit_thread_in_child() /
mc_exit_main_thread_in_child()) uniformly across all restart/branch
modes, instead of falling through to the real libpthread pthread_exit
and, on a __clone()-recreated thread, tripping libtsan's real
interceptor (which is not registered for that "fiber" thread and can
crash). Pre-restart modes forward to the real pthread_exit via a new
libtsan-bypassing libpthread_pthread_exit() handle, resolved with the
same dlopen+dlsym technique already used for pthread_timedjoin_np and
validated standalone in the prior commit's harness.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ion.h

Matches the header's existing convention (libc_exit/libc_abort use the
macro two lines below); interception.c's own pointer declarations
already use the raw attribute for a different, pre-existing reason
(exit_ptr/abort_ptr), so that file is untouched. Per Phase 3 Task 2
review's one Minor finding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…as live

DMTCP_RESTART_INTO_BRANCH/TEMPLATE were grouped with TARGET_BRANCH* in a
single case, skipping the mandatory thread_handle_after_dmtcp_restart()
call that every other wrapper performs for those two restart modes.
Split them into their own case that reports THREAD_EXIT_TYPE, calls
thread_handle_after_dmtcp_restart(), then falls through into the
unchanged TARGET_BRANCH*/TARGET_BRANCH_AFTER_RESTART body, matching the
mc_transparent_exit/mc_transparent_abort fallthrough precedent.

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

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces ThreadSanitizer (TSan) compatibility across DMTCP checkpoint/restart/fork paths: a TSan-safe bump allocator, /proc-based thread signal-block detection, fiber switching at fork/restart points, __clone-based thread recreation, a pthread_exit interceptor, renamed DMTCP pid-translation APIs, new headers, tests, and design/plan documentation.

Changes

TSan/DMTCP Compatibility Implementation

Layer / File(s) Summary
TSan-safe allocator and pthread map usage
src/common/mem.c, include/mcmini/mem.h, src/lib/record.c, include/mcmini/spy/checkpointing/record.h, src/lib/wrappers.c
Adds mc_ts_alloc bump allocator over a fixed BSS arena; wires it into record-mode list insertion (add_rec_entry_record_mode_ts) and rewrites pthread_map locking/allocation to use a sanitizer-bypassing mutex and this allocator.
Signal-based TSan thread detection
src/lib/tsan_support.c, include/mcmini/spy/checkpointing/tsan_support.h, CMakeLists.txt, test/tsan_support/test_thread_blocks_signal.c
Implements thread_blocks_signal() parsing /proc/self/task/<tid>/status SigBlk, wires the new source file into the build, and adds a standalone test validating detection for blocked, unblocked, and nonexistent TIDs.
DMTCP restart/fork fiber switching and thread classification
src/lib/dmtcp-callback.c, include/mcmini/spy/intercept/wrappers.h, src/lib/wrappers.c, src/common/multithreaded_fork.c, test/tsan_support/test_tid_from_descriptor_offset.c
Adds weak TSan fiber/fork hooks and __clone, a read-only pthread-descriptor TID helper, fiber switches around fork and restart, __clone-based thread recreation, reworked template_thread barrier counting that excludes TSan-internal threads, and mc_creating_internal_thread-gated pthread_create for helper threads; validated by a standalone descriptor-offset test.
pthread_exit interception and forwarding
include/mcmini/spy/intercept/interception.h, src/lib/interception.c, include/mcmini/spy/intercept/wrappers.h, src/lib/wrappers.c
Adds mc_pthread_exit wrapper with mode-dependent dispatch and libpthread_pthread_exit/libpthread_timedjoin_np cached forwarding handles/wrappers, wired through header declarations and the timed-join retry path.
DMTCP header API renames and metadata
include/dmtcp.h, src/common/multithreaded_fork.c, include/mcmini/model/pending_transitions.hpp, include/mcmini/model/state.hpp, include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp
Bumps DMTCP_PLUGIN_API_VERSION, adds checkpoint-header types, removes restore-buf declarations, renames pid-translation functions/macros with call-site updates, plus minor unrelated using-declaration and include fixes.
Fork/clone fiber and pthread_exit forwarding tests
test/tsan_support/test_fastpath_fork_clone_fiber.c, test/tsan_support/test_pthread_exit_forwarding.c
Adds standalone harnesses reproducing fast-path fork+clone thread recreation (with a buggy mode) and verifying real pthread_exit forwarding via dlopen/dlsym.
Design and implementation plan documentation
docs/superpowers/plans/*, docs/superpowers/specs/*
Adds Phase 1–3 implementation plans and design specifications describing the TSan port rationale, tasks, and verification steps.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ParentThread
  participant Kernel
  participant ChildOS
  participant TSanFiber

  ParentThread->>Kernel: _Fork() wrapped by TSan syscall hooks
  Kernel->>ChildOS: forked child process created
  ChildOS->>TSanFiber: __tsan_switch_to_fiber(create_fiber)
  ChildOS->>ChildOS: restart_child_threads_fast() via __clone
  ChildOS->>TSanFiber: recreated thread switches to fresh fiber after getcontext/setcontext
Loading
sequenceDiagram
  participant UserCode
  participant McPthreadExit
  participant Scheduler
  participant LibpthreadExit

  UserCode->>McPthreadExit: pthread_exit(retval)
  McPthreadExit->>McPthreadExit: check current mode
  alt pre-restart record/checkpoint mode
    McPthreadExit->>LibpthreadExit: forward to real pthread_exit
  else restart/branch/template/target mode
    McPthreadExit->>Scheduler: mark thread/process exit, transition control
  else unexpected template mode
    McPthreadExit->>McPthreadExit: abort
  end
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and relevant to the PR’s TSan thread/restart work, though it’s broader than the full changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tsan-record-thread-fix

Comment @coderabbitai help to get the list of available commands.

@gc00 gc00 requested a review from aayushi363 July 5, 2026 05:23
@gc00 gc00 added the enhancement New feature or request label Jul 5, 2026
@gc00 gc00 marked this pull request as ready for review July 5, 2026 05:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
test/tsan_support/test_fastpath_fork_clone_fiber.c (1)

129-129: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unchecked malloc result used as a clone stack pointer.

If allocation fails, stack becomes an offset of NULL, and it's later passed straight to clone()/__clone() as the child stack — undefined behavior / likely crash with a confusing failure mode instead of a clear assertion.

🔧 Suggested fix
-    void *stack = malloc(0x10000) + 0x10000 - 128; // 64 KB, intentionally leaked
+    void *raw_stack = malloc(0x10000); // 64 KB, intentionally leaked
+    assert(raw_stack != NULL);
+    void *stack = raw_stack + 0x10000 - 128;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/tsan_support/test_fastpath_fork_clone_fiber.c` at line 129, The clone
stack setup in test_fastpath_fork_clone_fiber.c uses the result of malloc()
directly in the stack pointer calculation, so an allocation failure would pass
an invalid stack to clone()/__clone(). Add an explicit NULL check for the malloc
call before deriving stack, and fail fast with a clear assertion or test error
in the relevant setup path inside the fiber/clone test helper.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-07-04-tsan-port-phase1-r1-implementation.md`:
- Around line 108-110: The bogus TID used in the thread_blocks_signal test is a
fixed positive value that can collide with a real thread ID and make the check
flaky. Update the assertion in the thread_blocks_signal test case to use a
sentinel value that cannot be a valid task ID, and keep the existing worker_tid
and self_tid coverage intact.

In
`@docs/superpowers/plans/2026-07-04-tsan-port-phase2-r2-r3-r4-implementation.md`:
- Around line 104-107: The TLS restoration in setTLSPointer() is using the wrong
arch_prctl invocation and success check. Update setTLSPointer(struct threadinfo
*ti) to call syscall(SYS_arch_prctl, ARCH_SET_FS, ti->fs) and
syscall(SYS_arch_prctl, ARCH_SET_GS, ti->gs) directly, and assert that each
returns 0 instead of != 0.

In `@docs/superpowers/specs/2026-07-04-tsan-port-phase1-r1-design.md`:
- Around line 117-119: The testing section conflicts with the Phase 1 plan by
saying no new unit-test harness is introduced, while the implementation
explicitly adds test_thread_blocks_signal.c under test/tsan_support. Update the
verification text in the design doc to reflect the new test file and align the
stated testing strategy with the actual files and approach used in this PR,
referencing the Phase 1 testing section and the test_thread_blocks_signal
symbol.

In `@test/tsan_support/test_fastpath_fork_clone_fiber.c`:
- Around line 87-94: The arch_prctl restore calls in setTLSPointer are using the
wrong argument order and inverted success checks, so the TLS/GS state is not
actually restored. Fix setTLSPointer to match getTLSPointer by passing
ARCH_SET_FS and ARCH_SET_GS as the second argument and asserting success with a
zero return; apply the same correction in the matching restore path in
src/lib/dmtcp-callback.c so both test and production code use the proper
arch_prctl invocation.

---

Nitpick comments:
In `@test/tsan_support/test_fastpath_fork_clone_fiber.c`:
- Line 129: The clone stack setup in test_fastpath_fork_clone_fiber.c uses the
result of malloc() directly in the stack pointer calculation, so an allocation
failure would pass an invalid stack to clone()/__clone(). Add an explicit NULL
check for the malloc call before deriving stack, and fail fast with a clear
assertion or test error in the relevant setup path inside the fiber/clone test
helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b0162a9-8176-403b-86c0-c1275fc99341

📥 Commits

Reviewing files that changed from the base of the PR and between 7a2d504 and cfba39d.

📒 Files selected for processing (27)
  • CMakeLists.txt
  • docs/superpowers/plans/2026-07-04-tsan-port-phase1-r1-implementation.md
  • docs/superpowers/plans/2026-07-04-tsan-port-phase2-r2-r3-r4-implementation.md
  • docs/superpowers/plans/2026-07-05-tsan-port-phase3-r5-implementation.md
  • docs/superpowers/specs/2026-07-04-tsan-port-phase1-r1-design.md
  • docs/superpowers/specs/2026-07-04-tsan-port-phase2-r2-r3-r4-design.md
  • docs/superpowers/specs/2026-07-05-tsan-port-phase3-r5-design.md
  • include/dmtcp.h
  • include/mcmini/mem.h
  • include/mcmini/model/pending_transitions.hpp
  • include/mcmini/model/state.hpp
  • include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp
  • include/mcmini/spy/checkpointing/record.h
  • include/mcmini/spy/checkpointing/tsan_support.h
  • include/mcmini/spy/intercept/interception.h
  • include/mcmini/spy/intercept/wrappers.h
  • src/common/mem.c
  • src/common/multithreaded_fork.c
  • src/lib/dmtcp-callback.c
  • src/lib/interception.c
  • src/lib/record.c
  • src/lib/tsan_support.c
  • src/lib/wrappers.c
  • test/tsan_support/test_fastpath_fork_clone_fiber.c
  • test/tsan_support/test_pthread_exit_forwarding.c
  • test/tsan_support/test_thread_blocks_signal.c
  • test/tsan_support/test_tid_from_descriptor_offset.c

Comment on lines +108 to +110
assert(thread_blocks_signal(worker_tid, SIGUSR1) == 1);
assert(thread_blocks_signal(self_tid, SIGUSR1) == 0);
assert(thread_blocks_signal(999999 /* bogus tid, should not exist */, SIGUSR1) == 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid a fixed positive bogus TID here.

999999 is not guaranteed to be absent; on a high-TID host it can collide with a live thread and make this test flaky. Use a sentinel that cannot exist as a real task ID instead.

Suggested fix
-  assert(thread_blocks_signal(999999 /* bogus tid, should not exist */, SIGUSR1) == 0);
+  assert(thread_blocks_signal(-1, SIGUSR1) == 0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert(thread_blocks_signal(worker_tid, SIGUSR1) == 1);
assert(thread_blocks_signal(self_tid, SIGUSR1) == 0);
assert(thread_blocks_signal(999999 /* bogus tid, should not exist */, SIGUSR1) == 0);
assert(thread_blocks_signal(worker_tid, SIGUSR1) == 1);
assert(thread_blocks_signal(self_tid, SIGUSR1) == 0);
assert(thread_blocks_signal(-1, SIGUSR1) == 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-07-04-tsan-port-phase1-r1-implementation.md`
around lines 108 - 110, The bogus TID used in the thread_blocks_signal test is a
fixed positive value that can collide with a real thread ID and make the check
flaky. Update the assertion in the thread_blocks_signal test case to use a
sentinel value that cannot be a valid task ID, and keep the existing worker_tid
and self_tid coverage intact.

Comment on lines +104 to +107
static void setTLSPointer(struct threadinfo *ti) {
assert(syscall(SYS_arch_prctl, 2, ARCH_SET_FS, ti->fs) != 0);
assert(syscall(SYS_arch_prctl, 2, ARCH_SET_GS, ti->gs) != 0);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target document around the cited lines
FILE="docs/superpowers/plans/2026-07-04-tsan-port-phase2-r2-r3-r4-implementation.md"
wc -l "$FILE"
sed -n '90,120p' "$FILE" | cat -n

# Find the helper and related mentions across the repo
rg -n "setTLSPointer|arch_prctl|ARCH_SET_FS|ARCH_SET_GS|SYS_arch_prctl" .

Repository: mcminickpt/deep-debug

Length of output: 5955


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding implementation and any local wrappers/comments
sed -n '90,140p' src/lib/dmtcp-callback.c | cat -n
sed -n '70,110p' test/tsan_support/test_fastpath_fork_clone_fiber.c | cat -n

# Check the kernel API constants from system headers, if available
grep -RIn --include='*.h' -E 'ARCH_SET_FS|ARCH_SET_GS|ARCH_GET_FS|ARCH_GET_GS|SYS_arch_prctl' /usr/include 2>/dev/null | head -n 40

Repository: mcminickpt/deep-debug

Length of output: 4574


Use the correct arch_prctl call here. setTLSPointer() should pass ARCH_SET_FS / ARCH_SET_GS directly and assert syscall(...) == 0; the extra 2 and != 0 make TLS restoration fail on success.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/superpowers/plans/2026-07-04-tsan-port-phase2-r2-r3-r4-implementation.md`
around lines 104 - 107, The TLS restoration in setTLSPointer() is using the
wrong arch_prctl invocation and success check. Update setTLSPointer(struct
threadinfo *ti) to call syscall(SYS_arch_prctl, ARCH_SET_FS, ti->fs) and
syscall(SYS_arch_prctl, ARCH_SET_GS, ti->gs) directly, and assert that each
returns 0 instead of != 0.

Comment on lines +117 to +119
No new unit-test harness is introduced — the project has no test harness yet
(per `CLAUDE.md`), and this is a runtime/scheduling fix best verified against
a real TSan target, per PLAN.txt's own phase methodology (section 7).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the testing section with the implementation plan.

This says no new unit-test harness is introduced, but Phase 1 explicitly adds test/tsan_support/test_thread_blocks_signal.c. Please update the text so the verification strategy matches the files in this PR.

Suggested fix
- No new unit-test harness is introduced — the project has no test harness yet (per `CLAUDE.md`), and this is a runtime/scheduling fix best verified against a real TSan target, per PLAN.txt's own phase methodology (section 7).
+ Phase 1 adds a small standalone harness (`test/tsan_support/test_thread_blocks_signal.c`) to lock in the `/proc` probe, while the restart path still needs the end-to-end TSan/DMTCP verification.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
No new unit-test harness is introduced — the project has no test harness yet
(per `CLAUDE.md`), and this is a runtime/scheduling fix best verified against
a real TSan target, per PLAN.txt's own phase methodology (section 7).
Phase 1 adds a small standalone harness (`test/tsan_support/test_thread_blocks_signal.c`) to lock in the `/proc` probe, while the restart path still needs the end-to-end TSan/DMTCP verification.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-04-tsan-port-phase1-r1-design.md` around lines
117 - 119, The testing section conflicts with the Phase 1 plan by saying no new
unit-test harness is introduced, while the implementation explicitly adds
test_thread_blocks_signal.c under test/tsan_support. Update the verification
text in the design doc to reflect the new test file and align the stated testing
strategy with the actual files and approach used in this PR, referencing the
Phase 1 testing section and the test_thread_blocks_signal symbol.

Comment on lines +87 to +94
static void getTLSPointer(struct threadinfo *ti) {
assert(syscall(SYS_arch_prctl, ARCH_GET_FS, &ti->fs) == 0);
assert(syscall(SYS_arch_prctl, ARCH_GET_GS, &ti->gs) == 0);
}
static void setTLSPointer(struct threadinfo *ti) {
assert(syscall(SYS_arch_prctl, 2, ARCH_SET_FS, ti->fs) != 0);
assert(syscall(SYS_arch_prctl, 2, ARCH_SET_GS, ti->gs) != 0);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether src/lib/dmtcp-callback.c has the same arch_prctl argument/assert pattern.
rg -n -B2 -A2 'ARCH_SET_FS|ARCH_SET_GS' src/lib/dmtcp-callback.c

Repository: mcminickpt/deep-debug

Length of output: 422


Fix the arch_prctl argument order and assert in both copies

setTLSPointer passes an extra leading 2 and checks != 0, so the restore call fails and the failure is treated as success. The same pattern is present in src/lib/dmtcp-callback.c, so the production TLS/GS restore path is affected too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/tsan_support/test_fastpath_fork_clone_fiber.c` around lines 87 - 94, The
arch_prctl restore calls in setTLSPointer are using the wrong argument order and
inverted success checks, so the TLS/GS state is not actually restored. Fix
setTLSPointer to match getTLSPointer by passing ARCH_SET_FS and ARCH_SET_GS as
the second argument and asserting success with a zero return; apply the same
correction in the matching restore path in src/lib/dmtcp-callback.c so both test
and production code use the proper arch_prctl invocation.

@xuyao0127 xuyao0127 requested a review from Copilot July 5, 2026 15:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants