Skip to content

lockfree/smr/nebr/thread_id

Platform-abstracted thread identifier for thread neutralization.

DEBRA+ needs a way to (a) identify the calling thread and (b) interrupt a stalled remote thread so the reclaimer can advance. The mechanism is platform-specific:

  • POSIX (Linux, macOS, BSD): the identifier is a Pthread. Remote neutralization is done by pthread_kill(tid, SIGUSR1) which delivers an asynchronous signal; the SIGUSR1 handler in signal.nim flips the target slot's pinned/neutralized flags. The sendSignal proc is the primary entry point on POSIX.
  • Windows: the identifier is the raw OS thread ID (uint32, the DWORD returned by GetCurrentThreadId). It is OS-managed — no kernel handle is allocated, duplicated, or stored. Remote neutralization uses SuspendThread / ResumeThread: the scanner opens a temporary handle via OpenThread(THREAD_SUSPEND_RESUME), suspends the target, directly flips its slot via the same pointer-arithmetic walk that the POSIX signal handler uses, resumes, then CloseHandles in a finally block. Handle lifetime is the duration of a single neutralizeRemoteSlot call. This avoids the async-signal-safety constraints of the POSIX path; signal.nim's handler-install machinery is a no-op on Windows. An earlier v0.10.0-dev design stored a duplicated handle in the slot; that design carried a bounded handle leak on slot churn and a use-after-close race between scanner and unregister. The on-demand OpenThread/CloseHandle design eliminates both by construction.

Deadlock constraint (both platforms): do NOT call DEBRA reclamation while holding a lock that other DEBRA-using threads might be blocked on. On POSIX the SIGUSR1 handler runs in the target's context and may need to acquire shared state; on Windows a SuspendThread'd target that holds a lock will deadlock the scanner if the scanner waits on that lock. Same hazard, different mechanism.

Cross-thread state mutation (the slot flip) lives in neutralizeRemoteSlot so the call site in neutralize.scanAndSignal stays platform-neutral.

ThreadId = object

Platform-abstracted thread identifier (POSIX arm).

proc currentThreadId(): ThreadId {.raises: [], tags: [], forbids: [].}

Get the ThreadId of the current thread.

proc isCurrent(tid: ThreadId): bool {.inline, raises: [], tags: [], forbids: [].}

Non-allocating identity check: returns true iff tid refers to the calling thread. Mirrors the Windows arm — on POSIX pthread_self() is cheap (no kernel call), so the no-alloc guarantee is structural rather than a meaningful perf win, but the API parity matters for the cross-platform call site in neutralize.scanAndSignal.

==

proc `==`(a, b: ThreadId): bool {.raises: [], tags: [], forbids: [].}

Compare two ThreadIds for equality.

proc `$`(tid: ThreadId): string {.raises: [], tags: [], forbids: [].}

Render a ThreadId for diagnostics. The auto-generated stringifier tries to format the underlying Pthread (an opaque struct pointer on some POSIX platforms) as an integer, which fails to compile; we render the pointer bits as hex instead. Used by unittest2.check when an equality assertion involving a ThreadId fails.

proc isValid(tid: ThreadId): bool {.raises: [], tags: [], forbids: [].}

Check if this ThreadId represents a valid thread.

proc sendSignal(tid: ThreadId; sig: cint): cint {.raises: [], tags: [],
forbids: [].}

Send a signal to the thread identified by tid. POSIX only.

Returns 0 on success. Returns ESRCH immediately when tid equals InvalidThreadId; otherwise returns the error code from pthread_kill.

Note: POSIX explicitly leaves pthread_kill with an invalid pthread_t as undefined behavior. Apple's libpthread happens to validate and return ESRCH, but glibc dereferences the handle and may segfault. Callers must only pass either a live thread's id or InvalidThreadId; passing a fabricated non-zero handle is unsupported.

proc unsafeThreadId(handle: Pthread): ThreadId {.raises: [], tags: [],
forbids: [].}

Create a ThreadId from a raw Pthread handle. For testing purposes only.

proc unsafeThreadIdFromInt(value: int): ThreadId {.raises: [], tags: [],
forbids: [].}

Create a ThreadId from an integer value. For testing purposes only - creates a fake thread ID.

template InvalidThreadId(): ThreadId