writeonce/docs/plan/exploration/linux/00-linux.md
shoney.arickathil c0b0dbb846 docs: audit all markdown against the code, fix findings, flatten status folders
- README: shipped concurrency/HTTP/WebSockets sat in the roadmap as "not yet
  available"; "no package manager" contradicted [deps]; the deps example
  would not have compiled (the key IS the module name)
- runtime/README: leads with wovm, wo-rt.c demoted to a historical section;
  dropped 2 nonexistent recipes, crates/rt, @gc refcounting, 13 suites -> 18
- employee + log-watcher READMEs claimed "does not compile"; both are gates
- error catalog: +10 emitted codes incl WO-E250, the only diagnostic the
  shipped query surface raises; recorded why the sweep rotted
- language-surface: group-by parses, then the typechecker refuses it
- 00-code-review + 00-link-audit re-run; history kept, not rewritten
- 48 dead Rust-era exploration links de-linked rather than re-pointed (their
  prose names the retired plan by number); successor map -> discarded.md
- 08-project-structure: compiler/plan/ never existed; corpus has 9 dirs, 5 empty
- releasing.md: dropped a --draft step the workflow never had
- new docs/00-doc-audit.md: findings + disposition, incl one row where the
  audit was wrong and the doc it accused was right
- status folders removed: 34 stories flat, status only in frontmatter; 252
  links recomputed from resolved paths; board/board-views/structure retaught
- story 24 -> in-progress, since frontmatter is now the only truth
- new iteration 38: fs mutation verbs + net.connect, the two capability
  families no iteration owned
- new iteration 39: gofiber/fiber v3.5.0 parity study. The ledger called
  CSRF/sessions unblocked by iteration 34's HMAC, but the runtime has no
  source of randomness at all
- linkcheck skips .dev/.superpowers: 0 broken paths, 0 bad anchors

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:20:22 +02:00

12 KiB

Linux Kernel Features

Kernel primitives that the writeonce binary can leverage, mapped to the architectural needs identified in docs/01-problem.md and docs/02-recovery.md.

Per-primitive reference cards

Each primitive has its own numbered file with the kernel source path (into reference/linux/), Rust FFI signature via libc, a minimal direct-syscall example, and the v1 port source. Use these when implementing the phase docs under docs/plan/.

# Primitive Used by
01 epoll — event-driven I/O multiplexing every runtime phase
02 eventfd — counter as fd, cross-flow wake phase 02, subscription wakeup
03 timerfd — timers as fds phase 02, phase 07 debounce
04 signalfd — signals as fds, graceful shutdown phase 04
05 inotify — filesystem events as fds phase 07, future register! subscription
06 sendfile — zero-copy file → socket phase 08
07 io_uring — async I/O ring buffers phase 3 (WAL fsync), future HTTP
08 mmap + madvise — memory-mapped files, page-cache hints phase 3 (storage engine)
09 fallocate + pread + pwritev2 — positional I/O & pre-allocation phase 3 (WAL + SSTables)
10 pidfd — process as fd, race-free supervision future supervisor
11 memfd_create — anonymous shared memory phase 3 (index build)
12 pwrite + fsync/fdatasync/sync_file_range/posix_fadvise — durability barriers phases 10, 11, 12 (storage foundations, WAL, engine cutover)

The list below is the original overview kept for context and for a handful of adjacent primitives (fanotify, splice/tee) that don't yet have their own reference card.

File Watching — Content Directory

Syscall Purpose
inotify Watch the content directory for file creates, modifications, and deletes. Triggers re-indexing and subscriber notification when articles change. Replaces the S3 + Lambda event pipeline entirely.
fanotify Alternative to inotify with broader scope (filesystem-level events). Useful if watching needs to span mount points or require permission-based filtering.

Async I/O — Server and Subscription Manager

Syscall Purpose
epoll Event-driven I/O multiplexing for the HTTP server and SSE connections. Handles many concurrent subscriber connections without a thread per client. Foundation for the async runtime.
io_uring Modern async I/O interface (Linux 5.1+). Supports batched, zero-copy submission of read/write/accept operations. Candidate for the embedded storage engine's disk reads and the HTTP server's socket handling in a single event loop.

Efficient File Serving — Content Delivery

Syscall Purpose
sendfile Zero-copy transfer from file descriptor to socket. Serves markdown files and static frontend assets directly from disk to the client without copying through userspace.
splice / tee Zero-copy data transfer between file descriptors via kernel pipe buffers. Useful for streaming .seg file reads directly to HTTP responses.

Embedded Storage — .seg Files and Indexing

Syscall Purpose
mmap Memory-map .seg index files for O(1)/O(log n) lookups by blog-title without loading entire files into heap memory. Keeps the storage engine's memory footprint proportional to working set, not total content size.
madvise Hint to the kernel about mmap access patterns (MADV_SEQUENTIAL for full scans, MADV_RANDOM for index lookups). Improves page cache behavior for both date-ordered listing and title-based retrieval.
fallocate Pre-allocate disk space for .seg files and append-only logs. Prevents fragmentation and ensures writes don't fail mid-operation due to disk pressure.
pread / pwritev2 Positional read/write without seeking. Allows concurrent reads from different offsets in the same .seg file without locking a shared file offset. Pairs well with io_uring for batched operations.

Event Notification — Subscription Manager

Syscall Purpose
eventfd Lightweight signaling between the file watcher thread and the subscription manager. When inotify detects a content change, eventfd wakes the async event loop to push diffs to SSE subscribers.
timerfd Timer as a file descriptor. Can drive periodic tasks (index compaction, subscriber keepalive pings) within the same epoll/io_uring event loop without a separate timer thread.

Process and Resource Management

Syscall Purpose
pidfd File descriptor for the process itself. Enables clean self-monitoring and graceful shutdown signaling within the single-binary model.
memfd_create Anonymous memory-backed file descriptors. Useful for transient storage (e.g., building a .seg index in memory before atomically swapping it to disk).

Runtime

  • Develop async runtime environment built on the kernel primitives above.
  • Core event loop: epoll or io_uring driving all I/O (file watch, HTTP, SSE, storage reads).
  • Single-threaded event loop with thread pool for blocking operations (disk I/O, index rebuilds).
  • All file descriptors (inotify, eventfd, timerfd, sockets) registered on one multiplexer — one process, one loop, no external dependencies.

Database Subscription

No SSE. No WebSocket. Subscriptions are handled at the kernel level using file descriptors and the event loop that already drives the rest of the system.

Why Not SSE or WebSocket

Both are HTTP-layer protocols that add framing, connection management, and protocol negotiation on top of what the kernel already provides. If the binary already owns the event loop (epoll / io_uring), the content directory (inotify), and the subscriber sockets (raw fds), there is no reason to layer another protocol on top. The kernel is the subscription engine.

Register Macro

A subscription is a registration of interest in a content query, bound to a file descriptor. The register macro wires a query pattern to the event loop at compile time:

register! {
    #{blog-title} => notify(fd)
}

This expands to:

  1. Parse the query pattern — #{blog-title} resolves to a specific article's sys_title key in the .seg index.
  2. Bind to inotify watch — The content file backing that blog-title gets an inotify watch descriptor. When the file is modified, inotify fires.
  3. Map watch → subscriber fd — The event loop maps the inotify event to every file descriptor that registered interest in that blog-title.
  4. Write to fd — The subscriber's socket fd receives the diff (or a notification payload) via a direct write() — no HTTP framing, no SSE data: lines, no WebSocket frames.

Flow

  inotify (content file changed)
       |
       v
  epoll wakes → lookup blog-title in subscription table
       |
       v
  for each registered fd:
       write(fd, diff_payload)

Subscription Table

The subscription table is an in-process map:

blog-title  →  Vec<RawFd>
  • Register: client connects → fd is added to the vec for the requested blog-title.
  • Deregister: client disconnects → fd is removed (detected via EPOLLHUP / EPOLLRDHUP).
  • Notify: inotify fires for a content file → resolve blog-title from path → write to all registered fds.

No broker, no message queue, no protocol layer. The kernel's fd lifecycle (epoll for readiness, inotify for content changes, close for cleanup) handles the full subscription lifecycle.

Query Patterns

The register macro supports patterns beyond single titles:

register! {
    #{blog-title}                  // single article by sys_title
    #[tag="rust"]                  // all articles matching a tag
    #[published > "2026-01-01"]    // date-range filter
    #*                             // all content changes
}

Each pattern resolves to a set of inotify watch descriptors. When the watched set changes (new article added that matches the filter), the subscription table updates automatically during re-indexing.

Every primitive above is reached via libc::<syscall> or libc::syscall(SYS_*, ...) — no custom assembly. The reasoning lives in ../assembly/ — three files covering why runtimes use asm at all (00-overview.md), what Go's reference/go/src/runtime/*.s actually contains (01-go-runtime-asm.md), and the writeonce policy that all of it is replaced by Rust stdlib + libc (02-writeonce-stance.md).