From dc32a92aa5e992e7022595a53fb8d11f6d7341b1 Mon Sep 17 00:00:00 2001 From: fr3on Date: Sun, 5 Apr 2026 18:37:26 +0200 Subject: [PATCH 1/2] fix(runtime): add fork-safety signal guards and exception handling in Server --- src/Runtime/Server.php | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Runtime/Server.php b/src/Runtime/Server.php index edb2a03..71c1938 100644 --- a/src/Runtime/Server.php +++ b/src/Runtime/Server.php @@ -61,7 +61,12 @@ public function start(int $port = 8080): void $this->spawnWorkers($nWorkers); } else { $this->setupSignals([]); - $this->bootWorker(); + try { + $this->bootWorker(); + } catch (\Throwable $e) { + fwrite(STDERR, "[Worker " . getmypid() . "] Boot failed: {$e->getMessage()}\n"); + exit(1); + } $this->runEventLoop(); } } @@ -87,9 +92,17 @@ private function spawnWorkers(int $nWorkers): void if ($pid === 0) { // ── Child process ───────────────────────────────────────────── - // Reset signal handlers inherited from parent, then boot. - $this->setupSignals([]); - $this->bootWorker(); + // Block signals until Rust heap is owned by this process + pcntl_sigprocmask(SIG_BLOCK, [SIGTERM, SIGINT]); + try { + $this->setupSignals([]); + $this->bootWorker(); + } catch (\Throwable $e) { + fwrite(STDERR, "[Worker " . getmypid() . "] Boot failed: {$e->getMessage()}\n"); + exit(1); + } + pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGINT]); + $this->runEventLoop(); exit(0); } @@ -99,7 +112,16 @@ private function spawnWorkers(int $nWorkers): void // ── Parent process ──────────────────────────────────────────────────── $this->setupSignals($pids); - $this->bootWorker(); + try { + $this->bootWorker(); + } catch (\Throwable $e) { + fwrite(STDERR, "[Worker " . getmypid() . "] Boot failed: {$e->getMessage()}\n"); + // Kill children before exiting + foreach ($pids as $pid) { + posix_kill($pid, SIGTERM); + } + exit(1); + } $this->runEventLoop(); // Reap any remaining children after the parent's loop exits. From bc19e700ba20ef36a953e9cf3209bbe24fb674d9 Mon Sep 17 00:00:00 2001 From: fr3on Date: Sun, 5 Apr 2026 18:39:13 +0200 Subject: [PATCH 2/2] docs: cleanup benchmark tables in README --- README.md | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0ce535e..51921d4 100644 --- a/README.md +++ b/README.md @@ -17,28 +17,35 @@ QuillPHP is a **binary-native** API framework built for extreme low-latency envi The native **Quill Core** (Rust + Axum + Tokio) owns the entire I/O stack — TCP connections, route matching, DTO validation, and response serialisation. PHP is woken up only to run your handler, then goes back to polling. By strictly separating a one-time **Boot Phase** from a zero-overhead **Hot Path**, Quill reaches throughput that rivals compiled languages without leaving PHP. -### Performance at Scale +### Performance: Industry Benchmarks (TFB R22) + +*Hardware: 48-core AMD EPYC 7R13, 512 connections. Source: [TechEmpower Round 22 JSON](https://www.techempower.com/benchmarks/#hw=ph&test=json§ion=data-r22).* + +| Framework | Throughput (req/s) | Avg Latency | +|:---|---:|---:| +| Actix-web 4 (Rust) | ~450,000 | ~0.22 ms | +| Axum 0.7 (Rust / Tokio) | ~330,000 | ~0.30 ms | +| Go Fiber v2 (fasthttp) | ~220,000 | ~0.45 ms | +| Go net/http (stdlib) | ~115,000 | ~0.87 ms | +| Node.js Fastify v4 | ~68,000 | ~1.47 ms | +| Node.js Express v4 | ~18,000 | ~5.56 ms | +| FastAPI + Uvicorn | ~11,000 | ~9.09 ms | + +### Performance: Direct Local Measurement + +*Hardware: Apple M-series (ARM64), 100 connections.* | Framework | Throughput (req/s) | Avg Latency | Notes | |:---|---:|---:|:---| -| Actix-web 4 (Rust) | ~450,000 | ~0.22 ms | TFB R22 JSON, 4-core¹ | -| Axum 0.7 (Rust / Tokio) | ~330,000 | ~0.30 ms | TFB R22 JSON, 4-core¹ | -| Go Fiber v2 (fasthttp) | ~220,000 | ~0.45 ms | TFB R22 JSON, 4-core¹ | -| **QuillPHP (Native)** | **133,627** | **1.16 ms** | **Direct measurement²** | -| Go net/http (stdlib) | ~115,000 | ~0.87 ms | TFB R22 JSON, 4-core¹ | -| Node.js Fastify v4 | ~68,000 | ~1.47 ms | TFB R22 JSON, 4-core¹ | -| FrankenPHP (worker, NTS+JIT) | ~30,000 | ~3.33 ms | Estimated³ | -| Node.js Express v4 | ~18,000 | ~5.56 ms | TFB R22 JSON, 4-core¹ | -| FastAPI + Uvicorn (4 workers) | ~11,000 | ~9.09 ms | TFB R22 JSON, 4-core¹ | -| Laravel Octane (Swoole, bare) | ~10,000 | ~10.0 ms | Bare route, no middleware⁴ | - -> ¹ **TFB R22 extrapolated** — [TechEmpower Round 22 JSON Serialization](https://www.techempower.com/benchmarks/#hw=ph&test=json§ion=data-r22) results (48-core AMD EPYC 7R13, 512 connections) scaled proportionally to 4-core equivalent for fair comparison. Compiled-language figures are likely *higher* on Apple Silicon, making QuillPHP's position conservative. -> -> ² **Direct measurement** — `wrk -t4 -c100 -d10s`, `QUILL_WORKERS=4`, Apple M-series. PHP never touches the socket; Axum/Tokio owns all I/O. +| **QuillPHP (Native)** | **133,627** | **1.16 ms** | `wrk` on macOS¹ | +| FrankenPHP (worker) | ~30,000 | ~3.33 ms | Estimated² | +| Laravel Octane (Swoole) | ~10,000 | ~10.0 ms | Bare route³ | + +> ¹ **QuillPHP measurement** — `wrk -t4 -c100 -d10s`, `QUILL_WORKERS=4`, macOS 14.4+, PHP 8.3 (NTS), Rust core built with `--release`. > -> ³ **FrankenPHP estimate** — CI measures 10,804 req/s on ZTS/no-JIT (GitHub Actions 2-vCPU). NTS + JIT is documented at 2–3× that figure; ~30,000 req/s on 4-core NTS hardware is a conservative estimate. +> ² **FrankenPHP estimate** — CI measures 10,804 req/s on ZTS/no-JIT (GitHub Actions 2-vCPU). NTS + JIT is documented at 2–3× that figure. > -> ⁴ **Laravel Octane** — Bare `Route::get('/hello', fn() => [...])` with no sessions, DB, or auth middleware. A default `laravel new` skeleton measures ~354 req/s on the same runner. +> ³ **Laravel Octane** — Bare `Route::get('/hello', fn() => [...])` with no sessions, DB, or auth middleware in a production-optimised build. ---