added missing file

This commit is contained in:
Araq
2026-07-27 06:16:57 +02:00
parent a609f25a30
commit 4caba86162

View File

@@ -0,0 +1,190 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Virtual-thread worker pool.
##
## Included from `std/typedthreads`.
##
## Workers never exit. createThread pops one off the idle stack (or
## pthread_create / CreateThread a new one when the stack is empty), hands
## it a task via a per-worker BinSem, and the worker runs it, pushes itself
## back onto the idle stack, and posts the requesting virtual thread's
## `done` flag. joinThread waits on that flag — `pthread_join` is not used.
{.push stackTrace: off.}
const
PoolWorkerStackMask = 1024 * 256 * sizeof(int) - 1
PoolWorkerStackSize* = PoolWorkerStackMask + 1 - 4096
## Matches typedthreads' default ThreadStackSize for non-embedded targets.
type
TaskEntry* = proc (closure: pointer) {.nimcall, gcsafe, raises: [].}
## Worker-side entry: static function pointer plus opaque closure.
## typedthreads' adapter builds the closure from `TArg`.
Worker* = object
osThread: SysThread
wake: BinSem
# Per-worker TLS-emulation storage. With virtual threads on this worker
# all `var x {.threadvar.}` resolve to fields in `gcThread.tls` — so the
# storage persists across V's on the same worker. This is what makes
# `var allocator {.threadvar.}: MemRegion` keep its chunks across
# createThread/joinThread cycles.
gcThread: GcThread
# Task slot. Written by `poolDispatch` while the worker is parked on
# `wake`; read by `workerMain` once it wakes.
entry: TaskEntry
closure: pointer
base: ptr ThreadBase
next: ptr Worker
# ---------------- idle stack ----------------
var
poolLock: SysLock
idleTop: ptr Worker
initSysLock(poolLock)
proc pushIdle(w: ptr Worker) =
acquireSys(poolLock)
w.next = idleTop
idleTop = w
releaseSys(poolLock)
proc popIdle(): ptr Worker =
acquireSys(poolLock)
result = idleTop
if result != nil:
idleTop = result.next
result.next = nil
releaseSys(poolLock)
# ---------------- worker main ----------------
proc workerMain(arg: pointer) {.nimcall, gcsafe, raises: [].} =
let w = cast[ptr Worker](arg)
# One-shot per-worker setup. The stack-bottom mark is a frame in
# workerMain — every task call frame sits at a higher (deeper) address,
# so the bottom we register here remains valid for the worker's whole
# process-lifetime. Emulated-TLS bootstrap (`globalsSlot`) is done
# earlier, inside the C-callable thunk, since any Nim-convention proc
# call may touch threadvars in its prologue.
when not defined(boehmgc) and not defined(gogc) and not defined(gcRegions) and
not defined(gcDestructors) and not defined(gcHooks):
# Mirrors `usesDestructors` in system.nim (not exported, so duplicated).
var stackMark {.volatile.}: pointer
nimGC_setStackBottom(addr(stackMark))
when declared(initGC):
initGC()
when declared(threadType):
threadType = ThreadType.NimThread
while true:
waitBinSem(w.wake)
# Snapshot the task slot locally before pushing back to idle, since the
# slot can be overwritten as soon as another caller pops this worker.
let entry = w.entry
let closure = w.closure
let base = w.base
try:
entry(closure)
except CatchableError:
discard
# Per-V destruction handlers fire here and the seq is cleared so V+1
# starts with a fresh handler list (decision 1).
when declared(nimThreadDestructionHandlers):
for i in countdown(nimThreadDestructionHandlers.len-1, 0):
try: nimThreadDestructionHandlers[i]()
except CatchableError: discard
nimThreadDestructionHandlers.setLen 0
# `pushIdle` BEFORE `postBinSem(done)` so a join-then-redispatch can land
# on the same worker. Reversing order is correctness-preserving but
# silently degrades reuse.
pushIdle(w)
postBinSem(base.done)
# ---------------- platform-specific spawn ----------------
when defined(windows):
proc workerThunkWin(arg: pointer): int32 {.stdcall.} =
# Bind the emulated-TLS slot to this worker's GcThread BEFORE any
# nimcall proc runs — `workerMain` (and anything it calls) may touch
# threadvars in its function prologue, which under emulated TLS would
# deref a nil slot if globalsSlot hadn't been set for this OS thread.
when declared(globalsSlot):
let w = cast[ptr Worker](arg)
threadVarSetValue(globalsSlot, addr(w.gcThread))
workerMain(arg)
result = 0'i32
proc spawnOsWorker(w: ptr Worker) =
var dummy: int32 = 0'i32
let h = createThread(nil, PoolWorkerStackSize.int32, workerThunkWin,
cast[pointer](w), 0'i32, dummy)
if h <= 0:
raise newException(ResourceExhaustedError, "cannot create pool worker")
w.osThread = h
elif defined(genode):
# Genode keeps the legacy 1:1 model until its C++-side runtime adapts.
proc spawnOsWorker(w: ptr Worker) =
raise newException(ResourceExhaustedError,
"virtual-thread pool not implemented for Genode")
else:
proc workerThunkPosix(arg: pointer): pointer {.noconv.} =
# Bind the emulated-TLS slot to this worker's GcThread BEFORE any
# nimcall proc runs — see workerThunkWin for the rationale.
when declared(globalsSlot):
let w = cast[ptr Worker](arg)
threadVarSetValue(globalsSlot, addr(w.gcThread))
workerMain(arg)
result = nil
proc spawnOsWorker(w: ptr Worker) =
var attr: Pthread_attr
discard pthread_attr_init(attr)
discard pthread_attr_setstacksize(attr, PoolWorkerStackSize)
if pthread_create(w.osThread, attr, workerThunkPosix, cast[pointer](w)) != 0:
raise newException(ResourceExhaustedError, "cannot create pool worker")
discard pthread_attr_destroy(attr)
proc spawnWorker(): ptr Worker =
result = cast[ptr Worker](c_malloc(csize_t sizeof(Worker)))
zeroMem(result, sizeof(Worker))
initBinSem(result.wake)
spawnOsWorker(result)
proc workerOsThread*(w: ptr Worker): SysThread {.inline.} =
## Read accessor for `Thread[TArg].sys` plumbing.
w.osThread
# ---------------- public API ----------------
proc poolDispatch*(base: ptr ThreadBase; entry: TaskEntry; closure: pointer) =
## Hand `entry(closure)` to a worker. Demand-grown: pops the idle stack
## first, only spawns a fresh OS thread when the stack is empty.
resetBinSem(base.done)
var w = popIdle()
if w == nil:
w = spawnWorker()
w.entry = entry
w.closure = closure
w.base = base
base.worker = cast[pointer](w)
postBinSem(w.wake)
proc poolWaitDone*(base: ptr ThreadBase) =
## Block until the most recent `poolDispatch` on `base` completes.
waitBinSem(base.done)
{.pop.}