Hypersonic

 view release on metacpan or  search on metacpan

lib/Hypersonic/Event.pm  view on Meta::CPAN

package Hypersonic::Event;

use strict;
use warnings;
use 5.010;

our $VERSION = '0.19';

# Hypersonic::Event - Event backend registry and selection
#
# This module provides a central registry for event loop backends and
# automatic detection of the best available backend for the platform.
# All event loop implementations are JIT-compiled via XS::JIT::Builder.

# Backend registry - maps name to module
my %BACKENDS = (
    io_uring    => 'Hypersonic::Event::IOUring',     # Linux 5.1+ (fastest)
    epoll       => 'Hypersonic::Event::Epoll',       # Linux (fast)
    kqueue      => 'Hypersonic::Event::Kqueue',      # BSD/macOS (fast)
    iocp        => 'Hypersonic::Event::IOCP',        # Windows (fast, completion-based)
    event_ports => 'Hypersonic::Event::EventPorts',  # Solaris/illumos (fast)
    poll        => 'Hypersonic::Event::Poll',        # POSIX fallback
    select      => 'Hypersonic::Event::Select',      # Universal (Windows support)
);

# Priority order for auto-selection (first available wins).
#
# io_uring is first on Linux because it batches submissions through
# the SQ ring (one syscall per arm-poll batch vs one syscall per
# epoll_ctl), but as of 0.19 it operates in *readiness-only* mode -
# we use io_uring_prep_poll_add (level-triggered POLLIN) as a pure
# notification mechanism and let the main event loop's userspace
# accept()/recv() do the actual I/O, exactly like the epoll path.
#
# Pre-0.19 attempted to use io_uring's completion-based I/O
# (io_uring_prep_accept + io_uring_prep_recv) where the kernel did
# the I/O and returned the result via cqe->res, but that had two
# unfixable bugs:
#   (a) gen_get_fd discarded the accepted client_fd from cqe->res
#       for UD_ACCEPT and set fd=listen_fd, so the main loop's
#       accept(listen_fd) returned EAGAIN (kernel already had it)
#       and broke - leaking the connection.
#   (b) prep_recv used a single GLOBAL recv_buf shared across all
#       concurrent clients, corrupting each other's request data.
# This produced the "empty body + 18 SIGKILL cascade + 5140s
# wallclock" pattern in CPAN tester reports for Hypersonic 0.18 on
# cpansmoker-1023 (perl 5.38..5.43). The readiness-only design in
# 0.19 sidesteps both bugs.
#
# If a user's kernel doesn't behave as expected, the env var
# HYPERSONIC_EVENT_BACKEND=epoll (or any registered backend) and
# the constructor option `event_backend => 'epoll'` both override
# auto-detection.
my @PRIORITY = qw(io_uring epoll kqueue iocp event_ports poll select);

# Check if io_uring is available (Linux 5.1+ with liburing)
sub _has_io_uring {
    return 0 unless $^O eq 'linux';

    # Check kernel version >= 5.1
    my $ver = `uname -r 2>/dev/null` || '';
    my ($major, $minor) = $ver =~ /^(\d+)\.(\d+)/;
    return 0 unless $major && ($major > 5 || ($major == 5 && $minor >= 1));

    # Check for liburing.h in common locations
    for my $path (
        '/usr/include/liburing.h',
        '/usr/local/include/liburing.h',
        '/usr/include/x86_64-linux-gnu/liburing.h',
    ) {
        return 1 if -f $path;
    }

    return 0;
}

# Select best available backend for this platform
sub best_backend {
    my $class = shift;

    # Explicit override via env var. Useful for (a) developers who
    # know io_uring works on their kernel and want the throughput,
    # and (b) test runners that need to pin a known-good backend.
    # Validated against the registered backend list - a bogus value
    # falls back to auto-detection rather than dying, so a typo in
    # a user's shell rc doesn't blow up production.
    if (my $forced = $ENV{HYPERSONIC_EVENT_BACKEND}) {
        if ($BACKENDS{$forced}) {
            my $mod = $BACKENDS{$forced};
            eval "require $mod";
            return $forced if !$@ && $mod->available;
            # Fall through to auto-detect if the requested backend
            # isn't actually loadable / available on this host.
        }
    }

    # Windows prefers IOCP (falls back to select if unavailable)
    if ($^O eq 'MSWin32') {
        my $mod = $BACKENDS{iocp};
        eval "require $mod";
        return 'iocp' if !$@ && $mod->available;
        return 'select';
    }

    for my $name (@PRIORITY) {



( run in 0.827 second using v1.01-cache-2.11-cpan-9581c071862 )