diff --git a/scripts/kernel-floor.test.ts b/scripts/kernel-floor.test.ts new file mode 100644 index 0000000..0a8992e --- /dev/null +++ b/scripts/kernel-floor.test.ts @@ -0,0 +1,161 @@ +import { expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join, resolve } from "node:path"; + +// Guard for goceleris/celeris#891 items 14 and 28 (the kernel floors and the +// release stamp). The io_uring engine refuses every kernel before 5.19, because +// its cancels use the IORING_ASYNC_CANCEL flags that release added (celeris +// internal/engine/iouring, requireAsyncCancelFlags, celeris#682, #872), so the +// io_uring floor is 5.19. The epoll floor is 5.10 as celeris README.md states it. +// introduction.md names the current release; it is RELEASE below from the v1.6.0 +// tag on. Until that tag, the docs PR that changes these lines must not merge. +// The same file keeps the claims that review of the PR corrected: SQPOLL (no tier +// uses it, celeris#377), when Base and Optional run, what a low RLIMIT_MEMLOCK +// does to an explicit IOUring engine, and what Adaptive needs before it uses io_uring. +const ROOT = resolve(import.meta.dir, ".."); +const DOCS = join(ROOT, "src", "content", "docs"); +// RELEASE is the release stamp introduction.md must carry. It is deliberate that +// `bun test` fails after a version bump until this constant is edited too: the guard +// exists to force that edit, so the stamp and the text cannot drift apart. The epoll +// floor (5.10) is written in the PRESENT patterns and the header above; change them together. +const RELEASE = "1.6.0"; + +function read(file: string): string { + return readFileSync(file, "utf8"); +} + +function docPages(): string[] { + return [ + "configuration.md", + "core-concepts.md", + "deployment.md", + "engines.md", + "getting-started.md", + "introduction.md", + "performance.md", + ]; +} + +const docs = Object.fromEntries(docPages().map((f) => [f, read(join(DOCS, f))])); +const astro = read(join(ROOT, "src", "pages", "index.astro")); + +// Phrases that carried the old floors (io_uring 5.10+, epoll 3.10+, the 5.12 +// tier sentence, and the LTS framing) or the old release stamp. Each one is the +// exact text that stood on origin/main when this guard was written. +const STALE: string[] = [ + "(Linux 5.10+; needs", + "Linux 3.10+", + "3.10+", + "(kernel 5.10+, richer", + "Completion-based asynchronous I/O on Linux **5.10+**", + "| `Base` | 5.10+", + "A 5.12", + "Pre-5.10", + "pre-5.10", + "below 5.10", + "LTS-stable baseline", + "LTS-stable io_uring floor", + "known-good 5.10+ kernel", + "(≥5.10, ideally", + "`io_uring` engine needs kernel **5.10+**", + "only io_uring needs 5.10+", + "Kernel 5.10+", + "kernel is below 5.10", + "v1.5.8", + "`1.5.8`", + // Corrected after review: SQPOLL is never selected by any tier (celeris#377). + "with SQPOLL and", + "Adds SQPOLL and zero-copy send", + "is consulted for SQPoll on some kernels", + // Corrected after review: a healthy 6.x kernel runs the High strategy; Optional + // needs 6.0+ AND a failed provided-buffers probe. Only that probe drops a tier. + "a 6.0 or newer kernel adds", + "only when a `High` feature probe fails", + // Corrected after review: a low RLIMIT_MEMLOCK caps the workers (down to one); an + // explicit IOUring engine fails only if not even one ring fits. + "an explicit IOUring engine fails to start)", + "an explicit `IOUring` engine fails to start instead", + "under a forced `IOUring`\n it fails to start", + "io_uring setup\n fails", + // Corrected after review: Adaptive uses io_uring only from the 6.1+ fast tier + // (or 6.10+), so "epoll below 5.19" understates where it stays on epoll. + "Adaptive runs epoll on kernels below 5.19", + "Adaptive runs epoll on them", + "Adaptive runs\nepoll.", + "Adaptive runs epoll there", + // Corrected after review: the transparent fallback holds for Adaptive only. + "must pass or Celeris transparently", + "Celeris does **not** crash", +]; + +// Phrases that carry the v1.6.0 floors. Each must occur in the named page. +const PRESENT: [string, RegExp][] = [ + ["configuration.md", /`celeris\.IOUring`\s*\|\s*Linux 5\.19\+/], + ["core-concepts.md", /`celeris\.IOUring` \| Linux 5\.19\+/], + ["engines.md", /\*\*IOUring\*\*\s*\|[^\n]*Linux 5\.19\+/], + ["engines.md", /on Linux \*\*5\.19\+\*\*/], + ["engines.md", /\| `Base`\s+\| 5\.19\+/], + ["engines.md", /known-good 5\.19\+ kernel/], + ["engines.md", /\(5\.19 or later, ideally 6\.0 or later/], + ["engines.md", /On a Linux kernel before 5\.19 io_uring is\s+not available/], + ["getting-started.md", /`io_uring` engine needs kernel \*\*5\.19\+\*\*; the `epoll` engine and `Adaptive` need kernel \*\*5\.10\+\*\*, and Adaptive uses io_uring only on a 6\.1\+ kernel/], + ["engines.md", /\*\*Adaptive\*\* \(default\) \| `celeris\.Adaptive`\s+\| Linux 5\.10\+/], + ["deployment.md", /\*\*Kernel 5\.19\+\*\*/], + ["deployment.md", /it needs Linux 5\.10\+ and works out of the\s+box/], + ["deployment.md", /The kernel is below 5\.19/], + ["performance.md", /\(Linux 5\.19\+; needs `RLIMIT_MEMLOCK`\)/], + ["introduction.md", /tiered \*\*io_uring\*\* engine \(kernel 5\.19\+, with zero-copy send\s+on 6\.0\+\)/], + ["engines.md", /Under Adaptive the\s+server runs epoll; an explicit `IOUring` engine returns the error/], + ["engines.md", /`Optional` is chosen only on 6\.0\+ when the\s+provided-buffers probe has failed/], + ["engines.md", /when the provided-buffers probe fails on a 5\.19 kernel/], + ["engines.md", /Adaptive uses io_uring only on a 6\.1\+ kernel/], + ["engines.md", /runs on fewer workers, down to one, and fails only when not even one ring fits/], + ["deployment.md", /runs on fewer workers, and fails only when not even one ring fits/], + ["deployment.md", /Under Adaptive, Celeris falls back to epoll; an explicit `IOUring` engine returns the error/], + ["getting-started.md", /Adaptive uses io_uring only on a 6\.1\+ kernel/], + ["introduction.md", new RegExp(`\\*\\*\\[\`v${RELEASE.replaceAll(".", "\\.")}\`\\]`)], + ["introduction.md", new RegExp(`\\(currently \`${RELEASE.replaceAll(".", "\\.")}\`\\)`)], +]; + +// Every table row that names the io_uring engine and gives a Linux floor states 5.19+, and no 5.10. +// Feature-matrix rows (no `Linux` cell) are not floor claims and are not checked. +const IOURING_ROW = /^\|\s*(`(celeris\.)?IOUring`|\*\*IOUring\*\*)/; + +test("every table row for the io_uring engine states the 5.19 floor", () => { + const bad: string[] = []; + for (const [file, text] of Object.entries(docs)) { + for (const line of text.split("\n")) { + if (IOURING_ROW.test(line) && line.includes("Linux") && (!line.includes("5.19+") || line.includes("5.10"))) { + bad.push(`${file}: ${line.trim()}`); + } + } + } + expect(bad).toEqual([]); +}); + +test("no docs page carries a stale kernel floor or the old release stamp", () => { + const hits: string[] = []; + for (const [file, text] of Object.entries(docs)) { + for (const phrase of STALE) { + if (text.includes(phrase)) hits.push(`${file}: ${phrase}`); + } + } + expect(hits).toEqual([]); +}); + +test("each docs page states the v1.6.0 io_uring floor and epoll floor where it names them", () => { + const missing = PRESENT.filter(([file, re]) => !re.test(docs[file] ?? "")).map(([file, re]) => `${file}: ${re}`); + expect(missing).toEqual([]); +}); + +test("index.astro engine cards: io_uring is Linux 5.19+ and epoll is Linux 5.10+", () => { + const tagOf = (name: string) => astro.match(new RegExp(`name: "${name}",\\s*tag: "([^"]+)"`))?.[1]; + expect(tagOf("io_uring")).toBe("Linux 5.19+"); + expect(tagOf("epoll")).toBe("Linux 5.10+"); +}); + +test("introduction.md states the release on both lines, and both name the same version", () => { + const intro = docs["introduction.md"]; + expect(intro).toContain(`**[\`v${RELEASE}\`]`); + expect(intro).toContain(`(currently \`${RELEASE}\`)`); +}); diff --git a/src/content/docs/configuration.md b/src/content/docs/configuration.md index f89fc41..882469c 100644 --- a/src/content/docs/configuration.md +++ b/src/content/docs/configuration.md @@ -108,7 +108,7 @@ The I/O engine that drives the accept/read/write loop. The native engines are | ------------------- | ------------- | --------------------------------------------------- | | `celeris.Adaptive` | Linux only | **Default on Linux.** Starts on epoll, promotes connections to io_uring under load | | `celeris.Epoll` | Linux only | Edge-triggered epoll | -| `celeris.IOUring` | Linux 5.10+ | io_uring async I/O | +| `celeris.IOUring` | Linux 5.19+ | io_uring async I/O | | `celeris.Std` | any platform | **Default off-Linux.** Go `net/http` server | Defined in `celeris/config.go` (`EngineType`). The zero value resolves to `Adaptive` on diff --git a/src/content/docs/core-concepts.md b/src/content/docs/core-concepts.md index 3f4fc35..3653956 100644 --- a/src/content/docs/core-concepts.md +++ b/src/content/docs/core-concepts.md @@ -264,7 +264,7 @@ they differ only in how they talk to the kernel. | --- | --- | --- | | `celeris.Adaptive` (default on Linux) | Linux | Dynamically switches between epoll and io_uring based on load | | `celeris.Epoll` | Linux | Edge-triggered `epoll` | -| `celeris.IOUring` | Linux 5.10+ | `io_uring` async I/O | +| `celeris.IOUring` | Linux 5.19+ | `io_uring` async I/O | | `celeris.Std` | All platforms | Go's `net/http` server | The default engine is `Adaptive` on Linux and `Std` elsewhere (e.g. macOS dev diff --git a/src/content/docs/deployment.md b/src/content/docs/deployment.md index 85656c0..6983665 100644 --- a/src/content/docs/deployment.md +++ b/src/content/docs/deployment.md @@ -406,10 +406,9 @@ s := celeris.New(celeris.Config{ ### Running io_uring in a container io_uring is frequently **disabled by the platform** in containers — not by Celeris. -There are **two independent gates**, and *both* must pass or Celeris transparently -falls back to epoll. This is an optimisation, not a fix-or-fail: epoll is at +There are **two independent gates**, and *both* must pass for io_uring. Under Adaptive, Celeris falls back to epoll; an explicit `IOUring` engine returns the error when the syscalls are blocked (Gate 1) and runs on fewer workers when memlock is low (Gate 2). The Adaptive fallback is an optimisation, not a fix-or-fail: epoll is at throughput parity, so a container that can't use io_uring still runs at full speed. -When io_uring setup is denied, Celeris does **not** crash — the probe's +When io_uring setup is denied, the probe's `io_uring_setup` call returns an error, the io_uring tier is left unselected, and the adaptive engine runs on epoll (`celeris/internal/probe/probe.go` (`ProbeWith`)). Confirm which engine you actually got at runtime with `Server.EngineInfo()` (see @@ -426,7 +425,7 @@ of container-escape exploits — the same reasoning that led Google to turn it o across ChromeOS, Android, and its production fleet, and containerd to block it earlier. So on any modern Docker/Kubernetes setup io_uring is **off unless you opt back in**. A blocked `io_uring_setup` surfaces as `EPERM`, and Celeris drops to -epoll. +epoll under Adaptive. The blunt, **dev-only** way is to disable seccomp filtering entirely: @@ -484,12 +483,14 @@ ulimits: Other io_uring prerequisites (checked by the startup io_uring probe): -- **Kernel 5.10+** — Celeris's LTS-stable io_uring floor; older kernels fall through - to epoll (`celeris/internal/probe/probe.go` (`ProbeWith`)). -- **`CAP_SYS_NICE`** is consulted for SQPoll on some kernels - (`celeris/internal/probe/probe_linux.go` (`checkCapSysNiceLinux`)); not required for the basic io_uring path. +- **Kernel 5.19+** — the io_uring engine refuses older kernels: its cancels use the + `IORING_ASYNC_CANCEL` flags that 5.19 added (celeris#682), which older kernels lack; Adaptive stays on epoll on them + (`celeris/internal/engine/iouring/engine.go` (`requireAsyncCancelFlags`)). +- **`CAP_SYS_NICE`** is not a prerequisite: the probe reads it only as a fallback for an + SQPOLL flag that no tier uses ([celeris#377](https://github.com/goceleris/celeris/issues/377); + `celeris/internal/probe/probe_linux.go` (`checkCapSysNiceLinux`)). -You do not need to do anything special for epoll; it works on Linux 3.10+ out of the +You do not need to do anything special for epoll; it needs Linux 5.10+ and works out of the box. On macOS and Windows the engine is `std` (Go `net/http`). ### SO_REUSEPORT and multiple workers @@ -511,7 +512,7 @@ After=network.target [Service] ExecStart=/usr/local/bin/myservice -# Raise locked memory for io_uring (otherwise the engine falls back to epoll). +# Raise locked memory for io_uring (otherwise Adaptive falls back to epoll; an explicit IOUring engine runs on fewer workers, and fails only when not even one ring fits). LimitMEMLOCK=infinity # Plenty of file descriptors for high connection counts. LimitNOFILE=1048576 @@ -718,9 +719,9 @@ From the `X-Forwarded-For` chain, walked right-to-left and filtered against middleware). It is only trustworthy when `TrustedProxies` is set. **Why is my server using epoll when I asked for io_uring?** -The kernel is below 5.10, `RLIMIT_MEMLOCK` is too low, or the `io_uring_setup` -syscall is blocked by a sandbox. Celeris probes capabilities and falls back to epoll -(at throughput parity). See [Engines](/docs/engines). +The kernel is below 5.19 (or rejects the async-cancel flags), `RLIMIT_MEMLOCK` is too low, or the `io_uring_setup` +syscall is blocked by a sandbox. Under Adaptive, Celeris probes capabilities and falls back to epoll +(at throughput parity). An explicit `IOUring` engine returns the error instead when the kernel or the syscall check fails; with a low `RLIMIT_MEMLOCK` it runs on fewer workers, down to one, and fails only when not even one ring fits. See [Engines](/docs/engines). **What status do the probes return?** `200 {"status":"ok"}` when the checker passes, `503 {"status":"unavailable"}` when diff --git a/src/content/docs/engines.md b/src/content/docs/engines.md index e3ef14d..8c01ccb 100644 --- a/src/content/docs/engines.md +++ b/src/content/docs/engines.md @@ -34,9 +34,9 @@ in `celeris/config.go`. | Engine | Constant | Where it runs | What it is | | ---------------------- | ------------------- | -------------------- | ------------------------------------------------------------------------- | -| **Adaptive** (default) | `celeris.Adaptive` | Linux only | Runs epoll and io_uring behind one socket; switches based on live load. | -| **Epoll** | `celeris.Epoll` | Linux only | Edge-triggered epoll, per-core event loops, CPU pinning. | -| **IOUring** | `celeris.IOUring` | Linux 5.10+ | Completion-based async I/O via io_uring. The lowest-latency path. | +| **Adaptive** (default) | `celeris.Adaptive` | Linux 5.10+ | Runs epoll and io_uring behind one socket; switches based on live load. | +| **Epoll** | `celeris.Epoll` | Linux 5.10+ | Edge-triggered epoll, per-core event loops, CPU pinning. | +| **IOUring** | `celeris.IOUring` | Linux 5.19+ | Completion-based async I/O via io_uring. The lowest-latency path. | | **Std** | `celeris.Std` | Everywhere | Wraps Go's `net/http`. The portable fallback. | ### Adaptive — the default on Linux @@ -80,20 +80,27 @@ zero-copy `sendfile(2)` for static-file responses (`celeris/internal/engine/engi ### IOUring -Completion-based asynchronous I/O on Linux **5.10+**. Celeris detects the io_uring +Completion-based asynchronous I/O on Linux **5.19+**. Celeris detects the io_uring feature *tier* at startup and enables only what the running kernel supports (`celeris/internal/engine/tier.go`): | Tier | Kernel | Features enabled | | ------------ | --------- | ------------------------------------------------------------------------------- | -| `Base` | 5.10+ | LTS-stable baseline: linked SQE chains, single-shot accept/recv | -| `High` | 5.19+ | Multishot accept, COOP_TASKRUN; multishot recv with provided buffer rings is opt-in (see below) | -| `Optional` | 6.0+ | Adds SQPOLL and zero-copy send (`SEND_ZC`); 6.1+ swaps in DEFER_TASKRUN | - -You do not configure the tier — it is probed and applied automatically. A 5.12 -kernel transparently uses the `Base` feature set; a 6.1 kernel lights up the -`Optional` set. io_uring requires `RLIMIT_MEMLOCK` headroom for its rings and -provided buffers — see [Engine selection in practice](#engine-selection-in-practice). +| `Base` | 5.19+ (fallback) | Baseline: linked SQE chains, single-shot accept/recv. It runs only as a fallback: when the provided-buffers probe fails on a 5.19 kernel, when the kernel's feature bits do not match its version, or when `CELERIS_MAX_IOURING_TIER=base` caps the tier | +| `High` | 5.19+ | Multishot accept, COOP_TASKRUN (DEFER_TASKRUN on 6.1+), zero-copy send (`SEND_ZC`) on 6.0+; multishot recv with provided buffer rings is opt-in (see below) | +| `Optional` | 6.0+ (rare) | Zero-copy send and multishot accept on the task-run completion model, chosen only when the provided-buffers probe has failed. No SQPOLL: no tier uses it ([celeris#377](https://github.com/goceleris/celeris/issues/377)) | + +You do not configure the tier — it is probed and applied automatically. A kernel +before 5.19 gets no io_uring engine: the engine refuses to build. Under Adaptive the +server runs epoll; an explicit `IOUring` engine returns the error. On 5.19 and later +a healthy kernel runs the `High` set: 6.0 and later add zero-copy send, and 6.1 swaps +`COOP_TASKRUN` for `DEFER_TASKRUN`. `Optional` is chosen only on 6.0+ when the +provided-buffers probe has failed. Only that probe drops the tier: a failed +fixed-files or multishot-accept probe turns off that one feature and the tier stays +`High`. Adaptive uses io_uring only on a 6.1+ kernel that passes the startup probes, +with enough `RLIMIT_MEMLOCK`; below that it stays on epoll. io_uring requires +`RLIMIT_MEMLOCK` headroom for its rings and provided buffers — see +[Engine selection in practice](#engine-selection-in-practice). #### Tuning environment variables @@ -125,8 +132,8 @@ Std wraps Go's `net/http` server. The router, middleware, and `*Context` API are **identical** to the native engines; what you lose are the native fast paths (CPU pinning, zero-copy sendfile, engine-integrated WebSocket, the async-detach machinery). Std is the default and the only engine off Linux (macOS, Windows), and -it is what you run in tests on a dev laptop. On a pre-5.10 Linux kernel you still -get epoll — only io_uring needs 5.10+, and Adaptive falls back to epoll there. +it is what you run in tests on a dev laptop. On a Linux kernel before 5.19 io_uring is +not available, and Adaptive stays on epoll (epoll needs 5.10+; Adaptive uses io_uring only on a 6.1+ kernel). ### Native engines are Linux-only — and selecting one off-Linux is an error @@ -370,10 +377,10 @@ explicit choice only when you have a specific reason: | ----------------------------------------------------------------- | ----------------------------------------------------- | | The best engine, auto-selected (recommended) | *(leave `Engine` unset)* | | Deterministic behaviour / no runtime switching | `Engine: celeris.Epoll` | -| Lowest latency on a known-good 5.10+ kernel, willing to tune memlock | `Engine: celeris.IOUring` | +| Lowest latency on a known-good 5.19+ kernel, willing to tune memlock | `Engine: celeris.IOUring` | | Adaptive, but start hot on io_uring under known high concurrency | `Engine: celeris.Adaptive` + `WorkloadHint: …HighConcurrency` | | Portability / dev laptop / off Linux | `Engine: celeris.Std` *(or just leave unset off Linux)* | -| Pre-5.10 Linux kernel (no io_uring, but epoll works) | *(leave `Engine` unset → Adaptive falls back to epoll)*, or `Engine: celeris.Epoll` | +| Linux kernel before 5.19 (no io_uring) | *(leave `Engine` unset → Adaptive falls back to epoll)*, or `Engine: celeris.Epoll` | A few practical notes: @@ -381,12 +388,12 @@ A few practical notes: kernel/memlock requirements are awkward (containers with tight `RLIMIT_MEMLOCK`, conservative security policies). Epoll is at throughput parity for most request/response traffic and is the engine with zero-copy `sendfile`. -- **Force `IOUring`** when you have validated the kernel (≥5.10, ideally ≥5.19 or - ≥6.0 for the full feature tier) and the latency win matters. io_uring allocates +- **Force `IOUring`** when you have validated the kernel (5.19 or later, ideally 6.0 or later + for the full feature tier) and the latency win matters. io_uring allocates locked memory for its rings and provided-buffer pools, so raise the process `RLIMIT_MEMLOCK` (e.g. `LimitMEMLOCK=infinity` in a systemd unit, or - `--ulimit memlock=-1:-1` for a container). If memlock is too low, io_uring setup - fails — under Adaptive this means it never starts on / promotes to io_uring. In a + `--ulimit memlock=-1:-1` for a container). If memlock is too low, Adaptive never starts on or promotes to io_uring, and a forced + `IOUring` engine runs on fewer workers, down to one, and fails only when not even one ring fits. In a container you must **also** allow the io_uring syscalls in seccomp; see [Deployment → Running io_uring in a container](/docs/deployment#running-io_uring-in-a-container). - **Tune workers and buffers** with `Config.Workers` (default `GOMAXPROCS`), @@ -552,9 +559,9 @@ if p := s.EventLoopProvider(); p != nil { the handler returns, or `net/http` closes the connection out from under it. - **`PauseAccept`/`ResumeAccept` on std.** They return `ErrAcceptControlNotSupported` — always check the error with `errors.Is`. -- **Forgetting `RLIMIT_MEMLOCK` for io_uring.** Too-low memlock prevents io_uring - setup; under Adaptive this silently keeps you on epoll, under a forced `IOUring` - it fails to start. +- **Forgetting `RLIMIT_MEMLOCK` for io_uring.** Too-low memlock silently keeps + Adaptive on epoll; a forced `IOUring` engine logs a warning and runs on fewer + workers, and fails only when not even one ring fits. - **Forgetting to call `done()` after `Detach`.** The returned function *must* run (typically `defer done()` in the streaming goroutine) or the `*Context` leaks from its pool permanently (`celeris/context_response.go` (`Context.Detach`)). diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md index 187d349..b600392 100644 --- a/src/content/docs/getting-started.md +++ b/src/content/docs/getting-started.md @@ -15,7 +15,7 @@ Celeris source. | Requirement | Notes | | --- | --- | | **Go 1.27.2 or newer** | Celeris targets the current Go toolchain. | -| **Linux** for the high-performance engines | The `io_uring` engine needs kernel **5.10+**; the `epoll` engine needs kernel **3.10+**. | +| **Linux** for the high-performance engines | The `io_uring` engine needs kernel **5.19+**; the `epoll` engine and `Adaptive` need kernel **5.10+**, and Adaptive uses io_uring only on a 6.1+ kernel that passes the startup probes (epoll below that). | | Any other OS (macOS, Windows, BSD) | Supported via automatic fallback to the portable `std` (`net/http`) engine — no code changes needed. | You don't choose an engine to get started. On Linux, Celeris defaults to its diff --git a/src/content/docs/introduction.md b/src/content/docs/introduction.md index 633a72f..efc5aaa 100644 --- a/src/content/docs/introduction.md +++ b/src/content/docs/introduction.md @@ -15,7 +15,7 @@ name is Latin for *swift*. import "github.com/goceleris/celeris" ``` -The current release is **[`v1.5.8`](https://github.com/goceleris/celeris/releases/latest)** — +The current release is **[`v1.6.0`](https://github.com/goceleris/celeris/releases/latest)** — always pull the [latest release](https://github.com/goceleris/celeris/releases/latest) from GitHub. @@ -29,8 +29,8 @@ Celeris is a drop-in replacement for the `net/http` *server* — not for the `net/http` *types* you write handlers against, and not a framework that wraps the stdlib server. Underneath your routes sits a custom event loop: -- **On Linux** it runs a tiered **io_uring** engine (kernel 5.10+, richer - features on 5.19+) or an edge-triggered **epoll** engine with per-core CPU +- **On Linux** it runs a tiered **io_uring** engine (kernel 5.19+, with zero-copy send + on 6.0+) or an edge-triggered **epoll** engine with per-core CPU pinning. The default **adaptive** engine starts on epoll and switches to io_uring under sustained load, transplanting live keep-alive connections between the two in both directions. @@ -144,7 +144,7 @@ Operations. If you just want to ship, jump straight to - **Module:** `github.com/goceleris/celeris` · **package:** `celeris`. - **Version:** the running version is the exported `celeris.Version` constant - (currently `1.5.8`). Pin a version in your `go.mod` and upgrade deliberately. + (currently `1.6.0`). Pin a version in your `go.mod` and upgrade deliberately. - **Go toolchain:** Go 1.27.2+ (the module targets `go 1.27.2`). - **Platforms:** Linux for io_uring/epoll/adaptive; any OS Go supports for the std engine. The engine choice is automatic unless you set diff --git a/src/content/docs/performance.md b/src/content/docs/performance.md index d13b55f..269143a 100644 --- a/src/content/docs/performance.md +++ b/src/content/docs/performance.md @@ -219,7 +219,7 @@ connections to io_uring under sustained high load (`celeris/config.go` (`Workloa | --- | --- | | `Adaptive` (default) | You don't want to think about it. Starts epoll, switches to io_uring under load (connections transplant both ways). | | `Epoll` | You've measured that your workload is steadily low/medium concurrency and want to pin the behavior. | -| `IOUring` | You've measured a sustained high-concurrency keep-alive workload and want io_uring from the first connection (Linux 5.10+; needs `RLIMIT_MEMLOCK`). | +| `IOUring` | You've measured a sustained high-concurrency keep-alive workload and want io_uring from the first connection (Linux 5.19+; needs `RLIMIT_MEMLOCK`). | | `Std` | Non-Linux, or you want the plain net/http server for maximum portability. | As of v1.5.6 Adaptive **transplants** established keep-alive connections between diff --git a/src/pages/index.astro b/src/pages/index.astro index 1aac246..7739b00 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -54,12 +54,12 @@ api.POST("/users", createUser).Async()`; const engines = [ { name: "io_uring", - tag: "Linux 5.10+", - body: "Completion-based async I/O with multishot accept/recv, provided buffer rings and zero-copy sendfile. The lowest-latency path on modern kernels.", + tag: "Linux 5.19+", + body: "Completion-based async I/O with multishot accept and, on 6.0 and later, zero-copy send. The lowest-latency path on modern kernels.", }, { name: "epoll", - tag: "Linux 3.10+", + tag: "Linux 5.10+", body: "Edge-triggered, per-core event loops with CPU pinning. Proven, broad kernel support — and at throughput parity with io_uring.", }, {