package Hyperman;

use 5.010;
use strict;
use warnings;

our $VERSION = '0.16';

require XSLoader;
XSLoader::load('Hyperman', $VERSION);

1;

__END__

=head1 NAME

Hyperman - an event-loop PSGI server

=head1 SYNOPSIS

    use Hyperman;

    my $app = sub { [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Hello' ] ] };
    Hyperman->run( app => $app, port => 8080, workers => 4 );

    # async: a handler may return a Hyperman::Future of the response
    my $async = sub {
        my $env = shift;
        Hyperman->timer(0.5)->then(sub {
            [ 200, [ 'Content-Type' => 'text/plain' ], [ 'later' ] ];
        });
    };

=head1 DESCRIPTION

Hyperman is a PSGI server built on a prefork supervisor with a per-worker XS
event loop (L<Hyperman::Loop> over a pluggable readiness backend), aiming to
match a JIT HTTP server's throughput without a JIT. Handlers may return a
L<Hyperman::Future> (or any Future-compatible object) of the PSGI response;
the connection is parked while the worker keeps serving, and awaiting a
future inside a handler pumps the worker's own loop. Runs any Plack app via
C<plackup -s Hyperman>.

The entire implementation is XS: the loop, HTTP machinery, Futures, and the
process model live in C (F<include/hyperman/>), with per-package XS
interfaces (F<xs/>).

=head2 run

    Hyperman->run(
        app            => $psgi_app,   # required
        host           => '0.0.0.0',
        port           => 8080,        # a scalar, or an arrayref of ports
                                       # ([80, 8080]) for several plain
                                       # listeners sharing these options
        workers        => 0,           # 0/unset = one per CPU; 1 = in-process
                                       # dev mode (no supervisor)
        idle_timeout   => 60,          # close idle keep-alive conns (secs)
        header_timeout => 30,          # slow/partial request guard (secs)
        max_pipeline   => 32,          # requests per conn per wakeup
        reuseport      => 0,           # per-worker listeners (SO_REUSEPORT;
                                       # Linux accept scaling - regressed on
                                       # macOS, keep off there)
        access_log     => '/var/log/app.log',  # fast built-in Combined-log
                                       # writer (a path, or an open handle
                                       # like \*STDERR). Or pass a coderef
                                       # for custom logging:
                                       #   sub { my ($env,$status,$bytes)=@_ }
                                       # ($bytes is undef for streaming)
        max_requests_per_worker => 0,  # recycle a worker after N requests
        shutdown_grace => 30,          # bound on graceful drain (secs)
        affinity       => 0,           # pin worker i to core i%ncpu (Linux)
        http2          => 0,           # accept HTTP/2 (h2c, and h2 over TLS via
                                       # ALPN); needs the nghttp2 build
        tls_cert       => $cert_pem,   # serve HTTPS (needs the OpenSSL build,
        tls_key        => $key_pem,    # Hyperman->has_tls); both required
        tls_ca         => $ca_pem,     # verify client certs against this CA
        tls_verify     => 'require',   # none (default) | optional | require
        tls_sni        => {            # per-hostname certificates (SNI)
            'other.example' => { cert => $c2, key => $k2 },
        },
    );

The event backend is chosen automatically (kqueue, io_uring, epoll, then poll) and can
be forced with C<HYPERMAN_BACKEND>.

=head2 Multiple listeners

A single C<run> can bind several listeners, each independently plain or TLS -
the common case being plain B<:80> beside HTTPS B<:443>. Pass C<listen> an
arrayref of per-listener hashrefs; each takes its own C<port> (required) plus
any of C<host>, C<tls_cert>, C<tls_key>, C<tls_ca>, C<tls_verify>, C<tls_sni>,
C<http2>, and C<redirect_https>. A missing per-listener field falls back to the
top-level value of the same name, and the top-level C<port>/C<listen> are
mutually exclusive shorthands - C<< port => [80, 8080] >> is sugar for several
plain listeners sharing the top-level options.

    Hyperman->run(
        app     => $app,
        workers => 8,
        listen  => [
            { port => 80,  redirect_https => 443 },   # 301 every request to https
            { port => 443, tls_cert => $cert, tls_key => $key },
        ],
    );

C<redirect_https> makes a listener answer B<every> request with a C<301> to the
same host and request-target on the given https port (in C, before the app is
reached); the value is that target port, and a bare true value means the
standard C<443>. Its C<SERVER_PORT> and, for TLS listeners, C<psgi.url_scheme>
reflect the listener the request arrived on. All listeners serve the one C<app>.
Ports below 1024 (80, 443) still require the process to start as root or hold
C<CAP_NET_BIND_SERVICE>.

=head3 access_log

When C<access_log> is a path or an open filehandle, Hyperman formats an
Apache-style Combined Log Format line

    host - user [dd/Mon/YYYY:HH:MM:SS +ZZZZ] "METHOD URI PROTO" status bytes "referer" "ua"

entirely in C and appends it to the file, with no per-request Perl call on
the hot path. The timestamp is cached and recomputed at most once a second,
lines are buffered and flushed once per event-loop wakeup, and a file target
is opened once in the parent and shared C<O_APPEND> across workers so their
lines stay interleaved without tearing. Quoted fields are escaped, so a
hostile URI or User-Agent cannot forge a log line. C<REMOTE_ADDR> is captured
at accept and is also visible to the app in C<$env>.

Pass a coderef instead (C<< sub { my ($env, $status, $bytes) = @_ } >>) for
full control; that path calls back into Perl for every response.

=head2 HTTP/2

With C<< http2 => 1 >> (requires the nghttp2 build; C<< Hyperman->has_http2 >>
reports it), a connection whose first bytes are the HTTP/2 preface is served
as cleartext HTTP/2 (h2c, prior knowledge) via L<nghttp2|https://nghttp2.org>:
multiplexed streams, HPACK, and flow control, with each stream dispatched to
the app like any request (sync, C<Hyperman::Future>, or C<psgi.streaming>
responses all work). HTTP/1.1 remains the default on the same port.

=head2 TLS / HTTPS

With C<tls_cert>/C<tls_key> (PEM paths; requires the OpenSSL build,
C<< Hyperman->has_tls >>), the listener serves HTTPS: a non-blocking TLS
handshake is driven on the event loop, then reads/writes go through OpenSSL.
The certificate and key are validated once before forking, so a bad pair
fails fast. Combined with C<< http2 => 1 >>, ALPN negotiates B<h2> vs
B<http/1.1> per connection on the same port - the path browsers use for
HTTP/2. C<psgi.url_scheme> is C<https> for TLS requests, and C<HTTPS> is set
to C<on>.

B<Client certificates (mTLS).> C<tls_verify> (C<optional>/C<require>, with
C<tls_ca> as the trust anchor) requests and checks a client certificate;
C<require> fails the handshake without a valid one. The result is surfaced in
C<$env> the mod_ssl way: C<SSL_CLIENT_VERIFY> (C<SUCCESS>/C<FAILED>/C<NONE>),
C<SSL_CLIENT_S_DN>, C<SSL_CLIENT_I_DN>, plus C<SSL_PROTOCOL>/C<SSL_CIPHER>.

B<SNI (multiple certificates).> C<tls_sni> maps hostnames to their own
C<cert>/C<key>; the certificate is chosen from the TLS ServerName, falling
back to C<tls_cert>/C<tls_key>. See L</tls_reload> for replacing that map
while the server is running.

B<h2c Upgrade.> With C<< http2 => 1 >> over cleartext, an HTTP/1.1 request
carrying C<Upgrade: h2c> is answered with C<101 Switching Protocols> and the
connection continues as HTTP/2 (the original request becomes stream 1) - as
well as the prior-knowledge preface. Over TLS, h2 is chosen by ALPN instead.

With C<< workers > 1 >> a supervisor process manages the pool: a crashed
worker is respawned (exponential backoff on crash loops; clean exits respawn
immediately), C<SIGHUP> or C<SIGUSR2> recycles all workers with zero
downtime (new workers start, old ones drain gracefully), C<SIGUSR1> makes
every worker dump its stats to stderr, and C<SIGTERM> / C<SIGINT> drain -
bounded by C<shutdown_grace> - and exit. With C<< workers => 1 >> the server
runs in the calling process, no supervisor.

=head2 timer / io_ready

    my $f = Hyperman->timer($secs);          # Future, resolves after $secs
    my $g = Hyperman->io_ready($fh, 'r');    # Future, resolves on readiness

Both require a running worker loop. C<< Hyperman->loop >> returns the current
worker's L<Hyperman::Loop> (or undef outside one).

=head2 tls_reload

    my $n = Hyperman->tls_reload(\%sni);   # listeners rebuilt

Replace this worker's TLS certificates without replacing the process.

A listener's C<SSL_CTX> is built once, in the parent, before the fork -
so a certificate issued while the server is running is not served, and
C<SIGHUP> does not help because it re-forks from that same parent. This
is the way to pick one up. C<%sni> is the same shape C<run> takes,
C<< { host => { cert => $path, key => $path } } >>, and replaces the map
entirely rather than merging into it.

Call it B<from inside a worker> - from the app, or from a timer on
C<< Hyperman->loop >>. Each worker holds its own context pointer (the
fork copied it), so a reload changes that worker and no other; a pool
picks the new certificate up as each worker calls it. The listening
socket is never touched, so nothing is unbound and no connection is
refused, and connections already handshook keep the one they have. The
next connection B<that worker> accepts uses the new certificate.

Returns the number of listeners rebuilt: 0 outside a worker, 0 on a
plain-only server, and 0 when the rebuild would have served less than
what is already running - either because the default certificate would
not load, or because a per-host one would not and that host would have
silently dropped to the fallback. In every one of those cases the
running certificates are left exactly as they were, and the reason is
printed to C<STDERR>.

A worker respawned after a crash, or recycled by C<SIGHUP>, inherits the
parent's boot-time context again and needs its own C<tls_reload>. That
is a feature of where the context is built, not a bug here: whatever
drives the reload should drive it per worker rather than once.

=head2 stats

    my $s = Hyperman->stats;   # in-app, inside a worker
    # { requests => N, accepts => N, bytes_out => N, connections => N,
    #   backend => 'kqueue', pid => $$ }

Per-worker counters; returns undef outside a running loop.

=head2 detach

    my $fd = Hyperman::detach($env);

Hand a live HTTP/1 connection to the application. The server removes its
watchers for the socket, forgets the connection and B<does not close it>,
so from that point the application's own C<psgix.loop> watchers on that
descriptor are what drive it. This is the seam a protocol upgrade needs:
before it, a hijacked socket raced the server's own read watcher, which
consumed post-upgrade bytes into a buffer nothing drained.

The application writes its own upgrade response - the server's writer is
out of the picture - and returns C<[101, [], []]>, which is discarded.
Returns the real file descriptor.

    my $conn = $env->{'psgix.hyperman.conn'};   # [ fd, generation id ]

C<psgix.hyperman.conn> is both the ticket detach works from and the way an
application detects that detaching is possible at all: it is absent on
HTTP/2 and on servers that are not Hyperman. Detaching croaks, with the
reason, when the connection is gone or the ticket is stale, on HTTP/2, on
TLS, when output is still queued, or when it has already been detached.

Detach is legal from a Future continuation - asynchronous authentication
before an upgrade - but not from a C<psgi.streaming> responder, which has
already forced C<Connection: close>, nor once response bytes are queued.

The equivalent for a C consumer is the table's C<conn_detach>.

=head1 C ABI

Hyperman exposes a public C ABI so that B<other XS modules> can drive its
event loop and futures entirely from C - fd watchers and timers whose
callbacks run with no Perl call frame per event, and Hyperman::Future
create/settle/inspect without method dispatch. The motivating consumer is
L<DBIx::Loop>, whose pure-XS loop adapter watches database socket fds and
settles futures on the worker loop C-to-C.

=head2 Getting the header

The contract lives in F<include/hyperman/hm_abi.h>. Two ways to reach it:

B<ExtUtils::Depends> (no copying). Hyperman is a provider: building installs
F<hm_abi.h> and writes C<Hyperman::Install::Files>, so a dependent's
F<Makefile.PL> that says

    my $pkg = ExtUtils::Depends->new('My::Consumer', 'Hyperman');
    WriteMakefile( ..., $pkg->get_makefile_vars );

picks up F<hm_abi.h> on its include path automatically - but makes Hyperman
a configure-time dependency of the consumer.

B<Vendoring a pinned copy>. Because the table is resolved at runtime and the
header is pure declarations, a consumer that wants Hyperman to stay
B<optional> (as DBIx::Loop does) instead ships its own copy of F<hm_abi.h>
pinned at a known C<HM_ABI_VERSION> and simply fails its resolve when
Hyperman is not loaded.

Perl headers (F<EXTERN.h> / F<perl.h> / F<XSUB.h>) must be included before
F<hm_abi.h>.

=head2 The table

    #define HM_ABI_VERSION 2

    #define HM_ABI_READ  0x1        /* io_watch masks     */
    #define HM_ABI_WRITE 0x2

    #define HM_ABI_PENDING   0      /* future_state       */
    #define HM_ABI_DONE      1
    #define HM_ABI_FAILED    2
    #define HM_ABI_CANCELLED 3

    typedef struct hm_abi_timer hm_abi_timer;    /* opaque handle */

    typedef void (*hm_abi_io_cb)(pTHX_ int fd, int mask, void *ud);
    typedef void (*hm_abi_timer_cb)(pTHX_ void *ud);
    typedef void (*hm_abi_ready_cb)(pTHX_ SV *future, void *ud);

    typedef struct hm_abi {
        int abi_version;                          /* == HM_ABI_VERSION */

        /* loop handles (opaque hm_loop*) */
        void *(*cur_loop)(pTHX);
        void *(*loop_of_sv)(pTHX_ SV *loop_sv);
        SV   *(*sv_of_loop)(pTHX_ void *loop);

        /* persistent fd watchers, pure C dispatch */
        void (*io_watch)(pTHX_ void *loop, int fd, int mask,
                         hm_abi_io_cb cb, void *ud);
        void (*io_unwatch)(pTHX_ void *loop, int fd, int mask);

        /* one-shot timer with cancellation */
        hm_abi_timer *(*timer)(pTHX_ void *loop, double secs,
                               hm_abi_timer_cb cb, void *ud);
        void (*timer_cancel)(pTHX_ void *loop, hm_abi_timer *t);

        /* Hyperman::Future */
        SV  *(*future_new)(pTHX);
        int  (*is_future)(pTHX_ SV *sv);
        IV   (*future_state)(pTHX_ SV *f);
        void (*future_done)(pTHX_ SV *f, SV **vals, SSize_t n);
        void (*future_fail)(pTHX_ SV *f, SV *err);
        void (*future_on_ready)(pTHX_ SV *f, hm_abi_ready_cb cb, void *ud);

        /* await: pump the loop until f settles (re-entrant) */
        void (*run_until)(pTHX_ void *loop, SV *f);

        /* v2: detach a live HTTP/1 connection (see Hyperman::detach).
         * 0 ok; -1 no such connection or a stale id; -2 HTTP/2; -3 TLS;
         * -4 output still queued; -5 already detached. */
        int (*conn_detach)(pTHX_ void *loop, int fd, UV id);
    } hm_abi;

=head2 Hyperman::_abi_ptr

    my $iv = Hyperman::_abi_ptr;

Returns the address of the process-wide C<hm_abi> table as an integer (an
C<IV>). A consumer calls this once (at C<BOOT>, or lazily on first use),
C<INT2PTR>s it to a C<< const hm_abi * >>, and checks
C<< ->abi_version >= HM_ABI_VERSION >> (the version it was compiled
against) before using it. Not intended to be called from Perl for any other
purpose. C<Hyperman::_abi_selftest> exercises the whole table from C and
returns 1; it exists for Hyperman's own test suite.

=head2 Loop handles

C<cur_loop> returns the currently running loop or C<NULL> - inside a
Hyperman worker it is the worker's loop, so a consumer resolving at request
time needs no loop object at all. C<loop_of_sv> unwraps a L<Hyperman::Loop>
SV (croaks on anything else); C<sv_of_loop> returns the loop's blessed
wrapper SV (+1, caller owns), the same shared wrapper C<psgix.loop> uses.
The handle is opaque; pass it back to every loop-taking entry.

=head2 Watchers and timers

C<io_watch> installs a B<persistent> C watcher for one direction of one fd
(C<HM_ABI_READ> or C<HM_ABI_WRITE> - one direction per call). The callback
fires on every readiness event with no Perl call frame, receiving the fd,
the direction that fired, and C<ud>. Installing replaces any existing
watcher (C, Perl callback, or future) for that fd+direction; C<io_unwatch>
is idempotent and safe from inside the callback. fd must be below
Hyperman's fd ceiling (65536); a bad fd croaks.

C<timer> arms a B<one-shot> timer and returns an opaque handle;
C<timer_cancel> disarms a timer that has not fired yet. The handle dies
when the callback fires - never cancel after the fire (clear any stored
handle inside the callback). Cancel live timers before dropping a loop.

=head2 Futures

C<future_new> returns a pending L<Hyperman::Future> (+1, caller owns).
C<future_done> / C<future_fail> settle it and run its continuations through
the normal trampoline; both are no-ops on an already-settled future, and
values are copied, not stolen. C<future_state> returns the C<HM_ABI_*>
state without dispatch. C<future_on_ready> attaches a C continuation that
fires exactly once when the future settles - B<including cancellation>
(check C<future_state> to see which); this is how a consumer hooks
cancellation, e.g. issuing a database cancel when a caller cancels the
future. It fires immediately if the future is already settled.

C<run_until> pumps the loop until the given future settles. It is
re-entrant - calling it from inside a callback nests, exactly like
C<< ->get >> inside a Hyperman worker - so a blocking-style C<await> can be
built on it directly.

=head2 Contracts

Everything is single-threaded and fires on the loop thread, inside the
loop's dispatch:

=over 4

=item * C callbacks must B<not croak>. Trap errors (C<G_EVAL> around any
Perl you call) and settle a future instead; a longjmp out of the dispatch
loop leaves it inconsistent.

=item * C<ud> lifetime is the consumer's problem: it must stay valid until
the watcher is removed, the timer fires or is cancelled, or C<on_ready>
fires. Remove watchers before freeing their C<ud>.

=item * Callbacks may re-enter the table freely, including C<run_until>.

=item * The table only ever grows at the end. C<HM_ABI_VERSION> bumps on
any append; a consumer requires C<< abi_version >= >> the version it was
written against.

=back

=head2 Example: resolve and use

    #include "hm_abi.h"     /* via ExtUtils::Depends, or a vendored copy */

    static const hm_abi *HM = NULL;   /* resolved on first use */

    static const hm_abi *my_hm(pTHX) {
        if (!HM) {
            dSP; int n;
            ENTER; SAVETMPS; PUSHMARK(SP); PUTBACK;
            n = call_pv("Hyperman::_abi_ptr", G_SCALAR | G_EVAL);
            SPAGAIN;
            if (!SvTRUE(ERRSV) && n > 0) {
                IV p = POPi;
                if (p) {
                    const hm_abi *a = INT2PTR(const hm_abi *, p);
                    if (a->abi_version >= HM_ABI_VERSION) HM = a;
                }
            } else if (n > 0) (void)POPs;
            PUTBACK; FREETMPS; LEAVE;
        }
        return HM;   /* NULL => Hyperman absent or too old: fall back */
    }

    /* watch a DB socket; on readable, collect the result and settle */
    static void on_db_readable(pTHX_ int fd, int mask, void *ud) {
        my_req *r = (my_req *)ud;            /* must not croak */
        if (collect_result(r) == DONE) {
            HM->io_unwatch(aTHX_ r->loop, fd, HM_ABI_READ);
            if (r->timeout) {
                HM->timer_cancel(aTHX_ r->loop, r->timeout);
                r->timeout = NULL;
            }
            HM->future_done(aTHX_ r->future, &r->result_sv, 1);
        }
    }

    /* fire a query: future + fd watch + timeout, all C */
    void *loop = HM->cur_loop(aTHX);         /* the worker's loop */
    r->future  = HM->future_new(aTHX);
    HM->io_watch(aTHX_ loop, db_fd, HM_ABI_READ, on_db_readable, r);
    r->timeout = HM->timer(aTHX_ loop, 5.0, on_db_timeout, r);
    HM->future_on_ready(aTHX_ r->future, on_settled_or_cancelled, r);

=head1 AUTHOR

LNATION C<< <email@lnation.org> >>

=head1 LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under the Artistic License 2.0.

=cut
