Acme-Parataxis

 view release on metacpan or  search on metacpan

lib/Acme/Parataxis/Compat.pm  view on Meta::CPAN

use v5.40;

package Acme::Parataxis::Compat v0.1.3 {
    use Errno       qw[EAGAIN EWOULDBLOCK];
    use IO::Handle  ();                       # loaded only: the overrides call IO::Handle::blocking() fully qualified
    use Carp        qw[croak];
    use Time::HiRes ();                       # loaded only: the overrides call Time::HiRes::time() fully qualified

    #
    # Transparent unblocking (CORE::GLOBAL overrides). This module is an opt-in
    # convenience: Acme::Parataxis->enable_transparent_unblocking() installs overrides
    # that make the blocking builtins `sleep`, `read` and `sysread` cooperative inside
    # scheduled fibers, and delegate to the raw CORE:: builtin everywhere else (top
    # level, foreign threads), so nothing outside the scheduler changes.
    #
    # The override subs are installed, not compiled with the module, so only code that
    # is *compiled after installation* is affected - exactly the gevent-style contract.
    # Every override calls the real builtin through its CORE:: name, so none of them can
    # recurse into themselves.
    #
    my $INSTALLED = 0;
    my @SAVED;    # [ name, saved-glob ] per installed override, for disable()

    # The readiness budget, in ms, that the read/sysread overrides give each await_read probe. A probe that comes
    # back short of it means a real wait timed out, so the override keeps waiting rather than block the process.
    my $PROBE_MS = 5000;

    # How much of that budget a probe has to burn before its -1 is read as "the handle is watchable, it just had
    # nothing to say" rather than "this platform cannot watch this handle at all". Win32's select() takes sockets
    # and nothing else, so a pipe or a regular file there answers -1 instantly, while a socket that truly timed out
    # takes the full budget. Anything under this fraction is the former.
    my $UNWATCHED = 0.9;

    # Write the framed read's result back into the caller's scalar (a reference to its
    # @_ slot), mimicking the builtin: undef (error) and 0 (EOF) leave the caller's
    # buffer alone; otherwise bytes before OFFSET are preserved and the read data is
    # placed at OFFSET.
    sub _store ( $slot, $off, $rc, $buf ) {
        return undef unless defined $rc;
        return 0 if 0 == $rc;
        $$slot = ( $off ? substr( $$slot // '', 0, $off ) : q{} ) . $buf;
        return $rc;
    }

    # Put a handle into non-blocking mode, answering the mode it was in, or undef when the platform will not.
    sub _try_nonblocking ($fh) {

        # `blocking` with no argument reports the current mode, and undef is its answer for a handle it cannot
        # interrogate: an already-closed glob, a dirhandle, a driver-supplied handle - and on Win32, a socket.
        # Winsock's FIONBIO can *set* a socket's mode but has no way to report one, so there the save half of a
        # save/restore does not exist. Nothing is lost by asking rather than assuming a platform: on the one
        # platform that cannot answer, the answer this read needs is already "no mode change", because sysread
        # hands back a short count on its own (see _read_nonblocking).
        my $was = IO::Handle::blocking($fh);
        return undef unless defined $was;

        # The setter answers with the mode it *replaced*, and -1 when it could not do the job at all. -1 is true, so
        # the return cannot be read as a success flag the way an undef could be; comparing against -1 is what tells
        # the two apart.
        my $replaced = IO::Handle::blocking( $fh, 0 );
        return defined $replaced && $replaced >= 0 ? $was : undef;
    }

    # Put a handle back the way the caller had it. The caller's own mode is what the handle looks like to every other
    # bit of code that touches it, including code compiled *before* installation, which still gets a raw blocking read
    # from this same handle. Restoring is what keeps transparent unblocking transparent.
    sub _restore_blocking ( $fh, $was ) {
        IO::Handle::blocking( $fh, $was ? 1 : 0 );
        return;
    }

    # Perform one read on a handle that is already known to be readable, WITHOUT letting a
    # short read park the OS thread.
    #
    # This is the whole point of the exercise. Perl's read() builtin does not return when *one* byte is available,
    # it keeps going until it has the *requested count* - so "await_read says ready, now read" froze the entire run
    # the moment a caller asked for more bytes than the peer had sent. Every fiber stalled, and because the fiber



( run in 1.515 second using v1.01-cache-2.11-cpan-b554fb43c41 )