God rested on the seventh day. The Temple's network stack never rested at all.
The sin
An idle Temple on the VDS, with nobody knocking, ate 100% of a CPU. The hosting company billed us for the sin of gluttony, and the glowing ones in Langley would have been proud.
The scheduler already has a HLT (Kernel/Sched.HC), but Seth reaches it
only after a full round in which no task was ready. Every wait in the network
stack was a while (...) Yield; loop, and a task that Yields is ready again
at once. So Seth never had a quiet round and never reached HLT:
-
NetHandlerstaring at an empty frame fifo -
accept()on the Confessional (:23) and HtServ (:80) -
recvfrom()on the DNS oracle (:53) - the TCP waits in connect, send, recv and close. Close could spin up to
TCP_CLOSE_TIMEOUTfor every connection a port scanner abandoned. The CIA scans, the Temple pays.
TASKf_IDLE did not help. It is bookkeeping for the CPU meter, it puts
nobody to sleep.
The repentance
-
Tcp.HC, Udp.HC: every wait loop now does
Sleep(1)instead of hosting company does. -
NetHandlerTask.HC: the handler lies down on
wake_jiffy(at most 10 ms) before it looks in the fifo, behind a locked XCHG (LXchgI64). A frame that lands in between finds it already lying down, so no wakeup is lost. -
NetFifo.HC: the NIC's IRQ calls the handler by name, the way God
called Samuel in the temple. It zeroes
wake_jiffyinNetFifoPushCopy(), so frames still go up the stack at once.
No kernel changes. Adam recompiles at boot, a reboot after
tools/update_vds.py is enough.
Testimony
QEMU, idle live CD, pcnet, 2 vCPU, TCG on a Mac:
| Host CPU at idle | |
|---|---|
| before | ~98–100% of a core |
| after | ~27–30% of a core |
What is left is mostly TCG emulating a 1 kHz timer and drawing the screen. Under KVM on the VDS it should be lower, not measured there yet. The Confessional answered three commands (accept, recv, send, close) in 0.16 s.
Not tested: HtServ over HTTP (it is off on the live CD unless
HtServEnable;) and the UDP side (NTP, DNS oracle) on their own.
What remains
Sleep(1) is still polling: one look per tick per waiting task. It costs
almost nothing only because TempleOS ticks at 1 kHz anyway. The righteous
way is wait channels, as in BSD tsleep()/wakeup(): the socket remembers
its waiting task and NetHandler wakes it when data arrives. That is a later
MR.
Be still, and know that I am God. Psalm 46:10.