Blog
Notes on io_uring, low-level networking and the internals of zerg.
-
C# Networking Deep Dive With io_uring — Part 7: fractalBuilding fractal, a thread-per-core static file server on io_uring with an eye on being the fastest at its one job. Why pread, sendfile and FileStream are all wrong for a pinned reactor, then extending Minima with true async file I/O: a third
IValueTaskSourcefacet that rides the same ring as the network, reads files straight into the send slab, and resumes the handler inline on the reactor thread with no thread hopping. Benchmarked against tuned nginx (~1.45x) and ASP.NETMapStaticAssets(~4.6x). -
C# Networking Deep Dive With io_uring — Part 6: NumbersSix implementations on the same i9 14900k box, two workloads (sync plaintext "OK" and an async
Task.Rundoing a trivialJsonSerializer.Serialize), measured with wrk and gcannon. io_uring r+w with IVTS inline tops the sync chart at 3.95M req/s but turns very unstable under async; once the handler leaves the reactor, the io_uring/threadpool variant drops to ~2.4M while epoll/threadpool and Kestrel's stock socket both hold ~3.6M sync / ~3.0M async. A hybrid io_uring recv + libc send lands in the middle. Includes a comparison-at-a-glance table. -
C# Networking Deep Dive With io_uring — Part 5: io_uring and the Thread PoolA rant, not the planned Kestrel integration. Why io_uring's frightening speed in the pinned-reactor model is conditional: it holds only while the handler never leaves the reactor, and the entire .NET backend (thread pool, async/await, Kestrel) is built on leaving it. The reactor deadlock when an off-reactor thread can't get its SQE submitted, Minima's eventfd wake, why SQPOLL only relocates the problem, and why epoll sidesteps the whole thing.
-
C# Networking Deep Dive With io_uring — Part 4: Zero Copy ReceiveA theoretical side-quest into io_uring zero copy receive (zcrx), where the NIC DMAs payload straight into registered user memory so the kernel never copies it. A high-level Minima vs MinimaZero comparison across buffer ownership,
ZCRX_IFQregistration,RECV_ZC, the 32-byte CQE token, the refill queue, and why zcrx collapses the multi-reactor model to a single ring. Scaffold only, no runnable code, since there was no zcrx-capable NIC to test on. -
C# Networking Deep Dive With io_uring — Part 3: Touching the BytesExtending the async model from part 2 to expose the received bytes themselves. We add a
byte*to each ring item, then introduceUnmanagedMemoryManager— aMemoryManager<byte>subclass that wraps an unmanaged pointer so the recv slabs can flow into the BCLMemory<byte>ecosystem (PipeReader, ReadOnlySequence, async APIs). -
C# Networking Deep Dive With io_uring — Part 2: Bridge the Async ModelWrapping the io_uring loop with C# async/await via
IValueTaskSource. A zero-allocation producer/consumer rendezvous between the kernel CQE dispatcher and the application handler, with a small SPSC ring that buffers back-to-back CQEs and a snapshot pattern that lets the handler drain in batches without chasing a moving tail. -
io_uring from scratch in C# — Part 1Bypassing every abstraction and using the kernel interface directly for high-efficiency TCP networking on Linux. We build a minimum viable io_uring wrapper in C# and walk through the setup, the SQE/CQE protocol, and the basic event loop.