Modules · Web · 0.63

Hyperman

An event-loop PSGI server: HTTP/1.1, HTTP/2, HTTP/3 and TLS in C.

A prefork supervisor with an XS event loop in every worker. A handler returns a response, or a future of one, and the connection is parked while the worker keeps serving. Any Plack application runs on it with plackup -s Hyperman, and this site does.

use Hyperman;

my $app = sub {
    my $env = shift;
    return [ 200, [ 'Content-Type' => 'text/plain' ], [ "hello\n" ] ]
        unless $env->{PATH_INFO} eq '/slow';

    # an async response: the worker serves other requests while this waits
    Hyperman->timer(0.2)->then(sub {
        # one counter, in the memory every worker maps
        my $n = Hyperman->arena->map('hits', slots => 64)->incr('slow');
        [ 200, [ 'Content-Type' => 'text/plain' ], [ "slow request $n\n" ] ];
    });
};

Hyperman->run(app => $app, port => $ENV{PORT} || 8080, workers => 4, arena_size => 8_000_000);

What you get

  • A loop in every worker

    A supervisor forks the workers and each runs its own XS event loop on kqueue, epoll, io_uring or poll, whichever the machine has.

  • Sync or a future

    A handler returns a PSGI response or a future of one. While a future is pending the connection is parked and the worker serves others. Streaming works as in any PSGI server.

  • HTTP/2, HTTP/3 and TLS

    With the nghttp2, ngtcp2 and OpenSSL builds: h2 by ALPN and h2c on the same port, HTTP/3 over QUIC advertised by Alt-Svc, SNI, client certificates and reloading a certificate without a restart.

  • Memory the workers share

    A Shared::Arena made before the fork: Hyperman->arena gives every worker the same maps, leases and counters, and a pool-wide publish and subscribe bus needs no Redis.

  • Refused at accept

    The denylist and the rate counters live in that arena, so a limit of 100 a minute is 100 for the pool, not 100 per worker, and a blocked peer is closed before a byte is read.

  • A C ABI

    Loops, watchers, timers, futures and the bus are reachable from another module's C. Punk, DBIx::Loop and Fetch's Hyperman loop use it.

In use

# any PSGI application, under plackup
plackup -s Hyperman --port 8080 --workers 4 app.psgi

# HTTP/2 and TLS on the same port (with the nghttp2 and OpenSSL builds)
plackup -s Hyperman --port 8443 --http2 1 \
    --tls_cert cert.pem --tls_key key.pem app.psgi

# choose the event backend instead of letting it choose
HYPERMAN_BACKEND=poll plackup -s Hyperman app.psgi

Fewer syscalls, said honestly

On the io_uring backend Hyperman can ask the kernel for each read up front, so the read stops being a system call of its own. Over 2,000 keep-alive requests on 50 connections, as its POD records it: 2.02 system calls a request with readiness, 1.02 in completion mode.

And it has not meant faster. On a virtualised Linux the same work costs about 4.2 microseconds of server CPU a request under io_uring and about 2.9 under epoll, so epoll stays ahead and io_uring stays off the automatic choice until that is understood on real hardware.

For C

hm_abi.h: another XS module drives the worker's loop and futures entirely from C, with fd watchers and timers whose callbacks run with no Perl frame per event. DBIx::Loop settles database futures this way. The table is resolved at run time, so a consumer can vendor a pinned copy and keep Hyperman optional.

Get it

cpanm Hyperman