Severity: medium — performance only, and only for off-reactor producers; but the Kestrel bridge and any thread-pool-hopping handler pays one write(2) syscall per operation.
Problem
ScheduleOnReactor has the right pattern: _postSignalPending guarantees at most one outstanding eventfd write per drain cycle — Reactor.Post.cs#L43-L52.
The other cross-thread producers wake unconditionally, one syscall per item:
Pure on-reactor handlers never hit these (the direct fast paths shortcut), so today's benchmarks don't show it — but the moment continuations hop threads (Kestrel transport, Task.Run, external awaits), it's a syscall per op through the back door.
Suggested fix
One shared _wakePending flag for all queues: producers Interlocked.Exchange after a successful enqueue and write the eventfd only on 0→1; the loop clears it at the top of the iteration before draining (same ordering argument as the existing DrainPostQ comment). The eventfd counter already coalesces kernel-side; this removes the redundant user-side syscalls.
Severity: medium — performance only, and only for off-reactor producers; but the Kestrel bridge and any thread-pool-hopping handler pays one
write(2)syscall per operation.Problem
ScheduleOnReactorhas the right pattern:_postSignalPendingguarantees at most one outstanding eventfd write per drain cycle — Reactor.Post.cs#L43-L52.The other cross-thread producers wake unconditionally, one syscall per item:
EnqueueReturnQ— Reactor.Drainers.cs#L54EnqueueRecycle— Reactor.Drainers.cs#L84EnqueueFlush— Reactor.Drainers.cs#L142SubmitClientOp— Reactor.RingHost.cs#L85EnqueueReturnQIncremental— Reactor.Loop.Incremental.cs#L139Pure on-reactor handlers never hit these (the direct fast paths shortcut), so today's benchmarks don't show it — but the moment continuations hop threads (Kestrel transport,
Task.Run, external awaits), it's a syscall per op through the back door.Suggested fix
One shared
_wakePendingflag for all queues: producersInterlocked.Exchangeafter a successful enqueue and write the eventfd only on 0→1; the loop clears it at the top of the iteration before draining (same ordering argument as the existingDrainPostQcomment). The eventfd counter already coalesces kernel-side; this removes the redundant user-side syscalls.