backend
nimony/lib/std/ioring/core/backend.nim
const CqSize: int64const InitialOpQueue: int64Starting capacity of a lane's deferred-op queue; it doubles when full,
const MaxOpQueue: int64up to this. Beyond it a submitter helps drain the queue instead.
type BackendRelays = object poll: proc (timeoutMs: int64): bool {.nimcall.} waits: bool close: proc () {.nimcall.} forgetFd: proc (fd: int32) {.nimcall.}
proc ioLanes(): int64Number of independent I/O lanes: one per pool worker, plus one trailing lane shared by every non-worker submitter.
proc ioLane(): int64The lane this thread owns. Every piece of per-thread ring state (the deferred op queue, the slot arena, the backend's poller instance) is indexed by this and by nothing else.
It is deliberately not
threadIdx: that is a threadvar defaulting to 0, so the main thread — which submits and polls throughwaitCompletions— reported the same index as worker 0 and the two drove one unlocked arena and one single-producer io_uring submission ring in parallel.Caveat: all non-worker threads share the trailing lane, so the ring still supports only one foreign submitter at a time (the usual "main thread drives the ring" shape). Ops submitted on that lane are drained by that thread's own
poll, i.e. by itswaitCompletions/pollCompletionsloop.type OpQueue = object lane: int64 own: LocalRing[OpContext] shared: FifoStripe[OpContext]
var gOpQueues: seq[OpQueue]proc initOpQueues()proc tryEnqueue(q: var OpQueue; op: OpContext; maxCapacity: int64): boolPush an op onto this lane. The unlocked ring is used only when the caller is the lane's owner, i.e. is the thread that will poll it:
isPoolWorker()rules out the trailing lane, whoseioLane()is the same number for every foreign thread.proc tryBulkDequeue(q: var OpQueue; bulkSize: int64; buf: var openArray[OpContext]): int64The lane owner's own ops first (they are the ones that were just submitted here), then whatever foreign threads left. The two were one FIFO before, so their relative order was never meaningful.
var gSlots: seq[SlotArena]proc initSlots()type TimerEntry = object at: Deadline slot: ref SlotObj not nil
type TimerHeap = object a: seq[TimerEntry]
proc len(h: TimerHeap): int64proc push(h: var TimerHeap; e: TimerEntry)proc remove(h: var TimerHeap; i: int64)Remove the entry at
i.var gTimers: seq[TimerHeap]proc initTimers()proc armDeadline(lane: int64; s: ref SlotObj not nil)Record the deadline of the op just allocated into
s. Aneverdeadline arms nothing, so the heap holds only ops that can actually expire.proc nextDeadline(lane: int64): DeadlineThe earliest deadline this lane is waiting on;
neverwhen there is nothing to wait for.proc waitMillis(lane: int64; requested: int64): int64How long the backend may actually block: what the caller asked for, or the time to the earliest deadline, whichever is sooner. This is what turns a fixed poll interval into "sleep exactly until something is due".
proc waitNanos(lane: int64; requestedMs: int64): int64waitMillisin nanoseconds, for a backend whose wait is atimespec.-1is "no bound at all", which only happens when the caller asked for none AND the lane is waiting on nothing that can expire.var gNextSeq: uint32var gCqLock: TicketLockvar gCq: seq[IoCompletion]var gCqHead: int64var gCqTail: int64var gCqCount: int64const IoTimedOut: int64Completion result for an op whose deadline passed.
ETIMEDOUTon Linux, and negative like every other failure the ring reports.const IoNoMemory: int64Completion result for an op that could not get a slot:
ENOMEM.proc complete(slot: ref SlotObj not nil; res: int64)Deliver
resfor the op inslotand release the slot: the caller must not touch it afterwards.proc newSlot(lane: int64; op: OpContext): ref SlotObjAllocate the slot for a dequeued
op. When there is no memory for one, the op completes right here withIoNoMemoryand the result isnil: the caller skips it.var gCancelInFlight: proc (s: ref SlotObj not nil): bool {.nimcall.}Set by a backend where the kernel keeps working on an op after this process has stopped waiting for it (io_uring, IOCP). Asks the kernel for the op back and answers whether the kernel owns it; if so the op is completed only when the kernel reports it (
completeFromKernel), because until then it may still write into the slot and the caller's buffer. The readiness backends leave itnil: a registration owns nothing, so dropping it is the whole of cancelling.proc cancelOp(lane: int64; s: ref SlotObj not nil; res: int64)Stop waiting for the op in
s: complete it withresnow, or, when the kernel still owns it, as soon as the kernel gives it back.proc completeFromKernel(s: ref SlotObj not nil; res: int64)Complete an op the kernel has just reported. If the ring had already given up on it, a failure is reported as the reason it gave up; a success is still a success, since the transfer happened and must not be lost.
proc expireDeadlines(lane: int64)Complete every op in this lane whose deadline has passed. Called by each backend after it waits, so a deadline fires whether or not any I/O did.
This is what makes "nothing parks forever" structural: an op cannot be submitted without a deadline, and every deadline is either met or arrives here. A caller parked on a peer that has gone quiet is resumed with
IoTimedOutrather than being left for the process's lifetime.