Revision history for Hyperman 0.30 2026-08-20 - A request body over 1MB is written to a temp file as it arrives instead of being held in memory, and psgi.input is a handle on that file. Measured end to end through a socket, a 128MB upload costs a worker about 15MB of RSS against roughly 275MB before. - Make one fewer syscall per request. The read loop drained to EAGAIN, so an ordinary keep-alive request cost three syscalls - a read that returned the request. - Header name comparison no longer depends on the operator's locale. hm_strncasecmp was the C library's, which folds case by LC_CTYPE, and every caller compares an HTTP header name or token - ASCII by RFC 9110. Under a Turkish locale the library folds 'I' to the dotless form, so "CONTENT-LENGTH" would stop matching "Content-Length" and the framing header would go unrecognised. 0.29 2026-08-20 - The Plack handler passes the bus params through. bus_slots, bus_slot_size and bus_groups. - A queue group registered before the server starts now works. 0.28 2026-08-20 - A cross worker message bus. One ring in shared memory, mapped before the fork, so every worker publishes into and reads from the same copy. 0.27 2026-08-18 - Hyperman->on_worker_start(sub { ... }): run code in every worker, after the fork, before its loop starts turning. - Hyperman->deny_add / deny_remove / deny_check / ratelimit_hit: the fork-shared arena, as class methods. 0.26 2026-08-18 - hm_abi v4: on_worker_start. A C consumer registers a callback before run(), and the server runs it once in every worker, after the fork, in the child, with that child's own loop, and before the loop starts turning. 0.25 2026-08-17 - Response compression: `compress => 1` on run() gzips responses on the way out (needs the zlib build; Hyperman->has_compression reports it, and without zlib the option is accepted and inert). - Hyperman NEVER touches a response that already carries a Content-Encoding. That one rule is the whole contract with whatever sits above: a framework serving precompressed .gz files sets `gzip` and its bytes go out untouched; a route that wants no compression sets `identity`, which is honoured and stripped. A plain response header, so any PSGI framework can use it. - Compression is OFF by default, under run() and under Plack::Handler::Hyperman alike, and both take the same options. Nobody's server starts compressing because they upgraded: whether the CPU is worth paying depends on whether bandwidth or the processor limits the deployment, which only its operator knows. - New run() options: compress, compress_min_length (default 1400, one MTU), compress_level. Plack::Handler::Hyperman passes all three through, so `plackup -s Hyperman --compress 1` is the same server. 0.24 2026-08-17 - More windows fixes 0.23 2026-08-16 - Hyperman attempt to build and serve on native Windows with no behaviour change on any platform it already ran on - The socket/IO surface now goes through one shim header - A WSAPoll readiness backend (backend_wsapoll.c) behind the existing hm_backend vtable, so Windows has the floor that poll(2) provides everywhere else - The process model is now explicit about what a platform can do. The supervisor half - fork, waitpid, the respawn backoff, the HUP/USR2 recycle and the POSIX signal plumbing - is compiled only where those exist. - Fixed a latent bug in the C ABI selftest, which asserted arena SEMANTICS (add a denylist entry, expect it to be found) without first asking whether this build has an arena. 0.22 2026-08-16 - t/27-sendfile.t asserted the worker's ABSOLUTE RSS stayed under 60MB after a 16MB download, but a worker's absolute size is a property of the perl build - static-libperl and threaded smokers idle at 62-83MB and failed the test with streaming working perfectly. It now measures the GROWTH across the download (baseline RSS before, probe after) and asserts it stays under 8MB 0.21 2026-08-16 - New Hyperman->tls_library: the runtime TLS library banner ("OpenSSL 3.0.13 ...", "LibreSSL 3.8.2"), undef without the TLS build - the supported way to tell the two stacks apart. - t/25-tls-session.t no longer fails on LibreSSL (OpenBSD, from CPAN Testers): LibreSSL implements no TLS 1.3 session resumption on either side of a connection, so the resumption checks could never pass there however correct the ticket key was. The ticket-key machinery under test is protocol independent, so on a LibreSSL stack those checks now run over TLS 1.2, where LibreSSL's tickets work. 0.20 2026-08-16 - Filehandle and IO-object response bodies stream instead of being slurped into one SV. A glob over a regular file lifts to (fd, position, remaining) - remaining is the Content-Length header when the response carries one. - use sendfile(2) on plaintext HTTP/1 file responses 0.19 2026-08-14 - Fixed a build failure on compilers older than GCC 4.7, which took out every perl on FreeBSD 9 (base cc is gcc 4.2.1): hm_ratelimit.h probed for __GNUC__ and then used the __atomic builtins, which arrived five releases later, so the arena added in 0.18 would not compile there at all. It now probes for the builtins themselves and falls back to the older __sync family, which also has everything the arena needs. 0.18 2026-08-13 - Abuse controls on a fork-shared arena: an IP denylist enforced at accept and fixed-window rate counters, both in one anonymous shared-memory region mapped before the workers fork, so a limit is exact across the pool rather than per worker. Everything fails open. - run() gains deny => [ips], deny_capacity and rate_capacity. A denylisted peer is dropped at accept, before a connection object is built or a byte is read; a new `denied` worker stat counts it. - hm_abi.h grows to ABI version 3 (append-only): deny_check, deny_add, deny_remove and ratelimit_hit, for consumers such as Punk's rate_limit. Older consumers are unaffected. - Rate-counter slots are reclaimed on demand: a slot whose window has rolled is reused by a new key before a live counter would be evicted, so the table drains when the keys that filled it go quiet rather than thrashing at capacity. 0.17 2026-08-12 - TLS session resumption now survives tls_reload. - HTTPS connections no longer pay a wasted SSL_read on every writable event. - A TLS connection whose SSL_read blocked wanting a writable socket no longer risks spinning the worker at 100% CPU. - SSL_MODE_RELEASE_BUFFERS, so OpenSSL's record buffers are handed back when they drain instead of being pinned for a connection's whole life - a keep-alive server is mostly idle connections. - A session id context is now set, without which OpenSSL declines to resume any session on a context that verifies peers: every mTLS connection paid a full handshake and a full chain verification. - SNI hostname lookup is a binary search over a sorted, lowercased table rather than a strcasecmp walk of every entry, which is a per-handshake cost that grew with the number of certificates served. - close_notify is no longer written for a connection dropped mid-handshake, where there is no session to shut down. - Tests no longer derive their listening port from $$. - A failed fork during a SIGHUP recycle no longer stores -1 in the child table, where the shutdown sweep would have turned it into kill(-1, SIGTERM) and signalled every process owned by the uid. 0.16 2026-08-12 - t/18-tls-advanced.t skips, rather than dies, when the host cannot mint a certificate. - t/16-http2.t no longer requires curl's --parallel. 0.15 2026-08-10 - Hyperman->tls_reload(\%sni): replace a worker's TLS certificates without replacing the process. 0.14 2026-08-08 - An inherited loop's kernel objects are now off-limits to the io and timer watch paths, not just to free. On Linux an epoll instance is a SHARED kernel object across fork - the fd is duplicated but both processes' epoll_ctl calls edit the same interest list - so a forked child that "cleaned up" watchers on a loop it merely inherited was silently deregistering its PARENT's fds. 0.13 2026-08-07 - A loop inherited across a fork no longer closes descriptors that are not its own. Every fd in a loop - the backend's own, the connection sockets, the access log - belongs to the process that created the loop, so a loop the child merely inherited is now freed without closing any of them. - Pre-size the PSGI env hash. A fresh HV starts with 8 buckets and the env carries 25-odd keys before the app adds its own, so every request paid two bucket split reallocs and their rehash walks. under profile S_hsplit alone was 4% of a worker's samples. hv_ksplit(64) at both env build sites (HTTP/1 and HTTP/2) removes it outright. - Store the common HTTP_* header keys through shared key SVs made once at init. - Fold header names to their canonical HTTP_ form before matching, so the Content-Length / Content-Type / Transfer-Encoding and Connection tests are plain memcmp on known-case bytes instead of case-blind scans of the wire bytes, per header, per request. The fold maps '-' to '_', so the special names also check the wire byte at the hyphen position - a literal "Content_Length" header stays HTTP_CONTENT_LENGTH rather than aliasing the real one, exactly as before. 0.12 2026-08-06 - io_uring: cancelling a timer could corrupt the heap. ur_del_timer freed the ur_timer immediately, but the timeout is already in flight in the kernel and its user_data IS that pointer, so the completion still arrived, was dereferenced, and freed a second time. - Workers report their own pid again on perl below 5.14. $$ is a plain SV that perl only refreshes inside pp_fork, and hm_spawn forks from C, so every worker in the pool kept reporting the supervisor's pid - and POSIX::getpid is `sub getpid { $$ }`, so that read wrong too. - t/15-process.t asserts the invariant directly: no worker may answer with the supervisor's pid. - t/22-abi.t asserted _abi_ptr returns a positive IV. It returns a raw pointer, and Solaris x86-64 maps shared objects high enough to set the sign bit, so the assertion was wrong rather than the table. 0.11 2026-08-06 - Connection detach, the protocol-upgrade seam: Hyperman::detach($env) hands a live HTTP/1 socket to the application. The server stops watching the fd, forgets the connection and does not close it, so the app's own psgix.loop watchers on that fd fire. The app writes its own upgrade response and returns a discarded sentinel ([101, [], []]). This is what a WebSocket layer needs; before it, a hijacked socket kept racing the server's own read watcher. - $env->{'psgix.hyperman.conn'} = [ fd, generation id ] - the ticket detach works from, and the way an app detects that detaching is possible (HTTP/2 streams and other servers do not provide it). - Detach is refused, with a specific message, for HTTP/2 and TLS connections, a stale ticket, a response still draining, and a second detach of the same connection. - 1xx and 426 responses now carry their real reason phrase: a 101 serialised as "HTTP/1.1 101 OK" before this. 0.10 2026-08-05 - Public C ABI for XS consumers 0.09 2026-08-04 - Chunked request bodies (Transfer-Encoding: chunked) are now decoded (HTTP/1). The decoded body is presented as psgi.input with a matching CONTENT_LENGTH; chunk extensions and trailers are accepted. - Security: harden HTTP/1 request framing against request smuggling (RFC 7230 3.3.3). Transfer-Encoding together with Content-Length, a repeated Transfer-Encoding, or a duplicate/malformed Content-Length (a sign, whitespace, trailing junk, or overflow) is rejected 400; an unsupported transfer coding is 501. All close the connection so no chunk data can be mis-framed as a following pipelined request. t/21-smuggling.t. - Security: the built-in redirect_https listener now truncates the reflected Host and request-target at the first control/space byte, so a bare LF in the Host header cannot inject or split the 301 response. Fuzz harness for the request parser under ASan/UBSan in tools/fuzz/. - Robustness: allocation helpers hm_xmalloc/hm_xcalloc/hm_xrealloc that croak on OOM, so no hot-path allocation dereferences a NULL result. tools/cppcheck.sh runs a static-analysis scan (clean). - PSGI env: add REMOTE_HOST (the peer address, like REMOTE_ADDR), REMOTE_PORT (the peer's port, captured at accept), and psgix.input.buffered => 1 (psgi.input is a seekable in-memory :scalar handle). Set for both HTTP/1 and HTTP/2. - Multiple listeners in one run(). Pass listen => [ {..}, .. ] to bind several listeners, each independently plain or TLS - the driving case being plain :80 beside HTTPS :443 - or port => [80, 8080] as sugar for several plain listeners sharing the top-level options. SERVER_PORT and psgi.url_scheme reflect the listener each request arrived on. - Built-in https redirect. A listener with redirect_https => $port answers every request with a 301 to the same host and target on that https port. 0.07 2026-08-03 - Build on OpenSSL older than 1.0.2. ALPN (SSL_CTX_set_alpn_select_cb), used to offer HTTP/2 over TLS, is absent before 1.0.2, so the module linked an undefined symbol and failed to load on such smokers. Guard it behind a version check. 0.06 2026-08-02 - Fast default access log. access_log now accepts a file path or an open handle in addition to a coderef: Hyperman then formats an Apache Combined Log Format line in C and appends it directly, with no per-request Perl call on the hot path. - Populate REMOTE_ADDR in the PSGI env (HTTP/1 and HTTP/2), captured from the peer at accept. Needed by the access log and expected by apps. 0.05 2026-08-02 - Various fixes to compile on perls older than 5.16. - Correct MIN_PERL_VERSION from 5.008003 to 5.010000 0.04 2026-08-02 - Link -lrt when clock_gettime needs it. backend_poll.c calls clock_gettime(CLOCK_MONOTONIC), which on glibc before 2.17 lives in librt, not libc. - t/12-loop.t: make the persistent-watcher subtest more robust by adding a watcher to prevent the test hanging like it has in some cpantesters environments. 0.03 2026-08-01 - TLS: build against pre-1.1.0 OpenSSL. Older releases lack OPENSSL_init_ssl(), the OPENSSL_INIT_LOAD_*_STRINGS constants, TLS_server_method(), and SSL_CTX_set_min_proto_version(); provide a compatibility shim (SSL_library_init + SSLv23_server_method + SSL_OP_NO_* protocol fencing) so hm_tls.h compiles instead of failing with undeclared-identifier errors on those hosts. Also map SSL_get1_peer_certificate to SSL_get_peer_certificate before 3.0. - Makefile.PL: bail out cleanly (NA, not FAIL) on native Windows. Hyperman needs fork, POSIX signals, sockets, sys/uio, and a kqueue/epoll/io_uring/poll backend, none of which MSWin32/MinGW provides (the build previously died on a missing ). - t/14-prod.t: bound the respawn/recycle/stats polls by wall-clock time instead of fixed iteration counts. A -9'd worker is respawned with exponential backoff, so on loaded smokers the replacement can lag; the fixed windows produced intermittent failures on the "request counter advanced" and "supervisor respawned a replacement worker" assertions. 0.02 2026-08-01 - io_uring backend: assign sqe->user_data directly instead of calling io_uring_sqe_set_data64(), which only exists in liburing >= 2.2 and left the .so with an undefined symbol on hosts with older liburing. - Supervisor: close a signal lost-wakeup race. The pool loop now keeps TERM/INT/HUP/USR1/USR2/CHLD blocked, reaps with WNOHANG, and waits via sigsuspend(), so a USR1 stats dump (or any signal) arriving while the pool is idle is serviced immediately instead of stalling until a worker happened to exit. Fixes intermittent t/15-process.t failures. 0.01 2026-07-31 First release. - Event-loop PSGI server: prefork supervisor + per-worker XS loop. Passes the full Plack::Test::Suite; runs any Plack app via `plackup -s Hyperman`. - Pluggable readiness backends behind one C vtable: kqueue (macOS/BSD), epoll (Linux), portable poll fallback, and an opt-in io_uring backend (raw syscalls, poll-mode ring). Selection is automatic; force with HYPERMAN_BACKEND. - Hyperman::Future: native XS array-slot Future-compatible async result. Handlers may return a Future (or any on_ready-compatible object, e.g. CPAN Future) of the PSGI response; get/await pump the worker's own loop re-entrantly. - Async PSGI: psgi.streaming/delayed responder with a streaming writer, psgix.io (dup'd client socket), psgix.loop, cancellation of the response Future on client disconnect, timer and io_ready Futures on the loop. - Production: worker respawn with crash-loop throttle, SIGHUP zero-downtime worker recycle, graceful TERM/INT drain, idle and slow-request timeouts, pipelining fairness cap, request size ceiling with 413, opt-in SO_REUSEPORT per-worker listeners (Linux), access_log callback, Hyperman->stats.