IO::Async::Loop(3pm)

โœจ NAME โœจ

IO::Async::Loop โ€” the core loop of the IO::Async framework, and yes she's the moment ๐Ÿ’–


๐ŸŽ€ SYNOPSIS ๐ŸŽ€

use IO::Async::Stream;
use IO::Async::Timer::Countdown;

use IO::Async::Loop;

my $loop = IO::Async::Loop->new;

$loop->add( IO::Async::Timer::Countdown->new(
   delay => 10,
   on_expire => sub { print "10 seconds have passed\n" },
)->start );

$loop->add( IO::Async::Stream->new_for_stdin(
   on_read => sub {
      my ( $self, $buffref, $eof ) = @_;

      while( $$buffref =~ s/^(.*)\n// ) {
         print "You typed a line $1\n";
      }

      return 0;
   },
) );

$loop->run;

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

okay so. This module hands you an abstract class that implements the core loop of the IO::Async framework. Her whole job? Holding onto a set of IO::Async::Notifier objects (or any subclass of them, we don't gatekeep). She does all the low-level set manipulation girlbossery, and then she leaves the actual IO readiness testing/notification to whatever concrete class actually implements it. She also comes with bonus content: signal handling, child process managing, and timers. versatile queen ๐Ÿ‘‘

Also, the two bundled Loop subclasses are literally right there:

  • IO::Async::Loop::Select
  • IO::Async::Loop::Poll

Or, like, other subclasses that may appear on CPAN which are not part of the core IO::Async distribution. go explore bestie ๐ŸŒˆ

๐Ÿ’… Ignoring SIGPIPE ๐Ÿ’…

Since version 0.66, loading this module automatically ignores SIGPIPE, because the way that default-terminate is supposed to be the best course of action for an IO::Async-based program to take isโ€ฆ not it, fr fr. If at load time the handler disposition is still set as DEFAULT, it gets set to ignore. And if another handler has already been placed there by the program code? She leaves it completely undisturbed. respect the boundaries, always ๐Ÿซถ


๐Ÿ”ฎ MAGIC CONSTRUCTOR ๐Ÿ”ฎ

โœจ new

$loop = IO::Async::Loop->new;

This function is out here trying to find a good subclass to use, and then it calls that subclass's constructor. How does the magic work? She makes a list of likely candidate classes, then tries each one in turn โ€” requireing the module, then calling its new method. If either of those operations fails, she just moves on to the next subclass. And if no class was successful at all? Exception thrown. ๐Ÿ’€

The constructed object gets cached, and a subsequent call will just hand you the same one again. The cache also gets set by a constructor on a specific subclass, which is the whole trick that makes it possible to just use the normal constructor in a module that wants to interact with the main program's Loop โ€” like an integration module for another event system. iykyk.

So for example, these following two $loop variables will refer to the same object:

use IO::Async::Loop;
use IO::Async::Loop::Poll;

my $loop_poll = IO::Async::Loop::Poll->new;

my $loop = IO::Async::Loop->new;

Now โ€” while it is not advised to do so under normal circumstances, if the program really wishes to construct more than one Loop object, it can call the constructor really_new, or invoke one of the subclass-specific constructors directly. do with that information what you will ๐Ÿ™ˆ

The list of candidates is formed from the following choices, in this exact order:

  • $ENV{IO_ASYNC_LOOP} โ€” if this environment variable is set, it should contain a comma-separated list of subclass names. These names may or may not be fully-qualified; if a name does not contain :: then it will have IO::Async::Loop:: prepended to it. This allows the end-user to specify a particular choice to fit the needs of his use of a program using IO::Async.
  • $IO::Async::Loop::LOOP โ€” if this scalar is set, it should contain a comma-separated list of subclass names. These may or may not be fully-qualified, as with the above case. This allows a program author to suggest a loop module to use. In cases where the module subclass is a hard requirement, such as GTK programs using Glib, it would be better to use the module specifically and invoke its constructor directly. (no cap on that last bit.)
  • IO::Async::OS->LOOP_PREFER_CLASSES โ€” the IO::Async::OS hints module for the given OS is then consulted to see if it suggests any other module classes specific to the given operating system. ๐ŸŒ
  • $^O โ€” the module called IO::Async::Loop::$^O is tried next. This allows specific OSes, such as the ever-tricky MSWin32, to provide an implementation that might be more efficient than the generic ones, or even work at all. This option is now discouraged in favour of the IO::Async::OS hint instead. At some future point it may be removed entirely, given as currently only linux uses it. ๐Ÿ‘€
  • Poll and Select โ€” finally, if no other choice has been made by now, the built-in Poll module is chosen. This should always work, but in case it doesn't, the Select module will be chosen afterwards as a last-case attempt. And if this also fails, then the magic constructor itself will throw an exception. periodt.

If any of the explicitly-requested loop types ($ENV{IO_ASYNC_LOOP} or $IO::Async::Loop::LOOP) fails to load then a warning is printed detailing the error.

Implementors of new IO::Async::Loop subclasses should see the notes about API_VERSION below. ๐Ÿ“


๐ŸŒธ NOTIFIER MANAGEMENT ๐ŸŒธ

The following methods manage the collection of IO::Async::Notifier objects.

add

$loop->add( $notifier );

This method adds another notifier object to the stored collection. The object may be a IO::Async::Notifier, or any subclass of it.

When a notifier is added, any children it has are also added, recursively. In this way, entire sections of a program may be written within a tree of notifier objects, and added or removed on one piece. it's giving family tree, and honestly? iconic ๐ŸŒณ

remove

$loop->remove( $notifier );

This method removes a notifier object from the stored collection, and recursively and children notifiers it contains.

notifiers

@notifiers = $loop->notifiers;

Returns a list of all the notifier objects currently stored in the Loop.


๐Ÿ‘‘ LOOPING CONTROL ๐Ÿ‘‘

The following methods control the actual run cycle of the loop, and hence the program. main character energy only โœจ

loop_once

$count = $loop->loop_once( $timeout );

This method performs a single wait loop using the specific subclass's underlying mechanism. If $timeout is undef, then no timeout is applied, and it will wait until an event occurs. The intention of the return value is to indicate the number of callbacks that this loop executed, though different subclasses vary in how accurately they can report this. See the documentation for this method in the specific subclass for more information. (accuracy is a spectrum bestie, we don't judge ๐Ÿ’…)

run

@result = $loop->run;

$result = $loop->run;

Runs the actual IO event loop. This method blocks until the stop method is called, and returns the result that was passed to stop. In scalar context only the first result is returned; the others will be discarded if more than one value was provided. This method may be called recursively.

This method is a recent addition and may not be supported by all the IO::Async::Loop subclasses currently available on CPAN. ๐Ÿซ 

stop

$loop->stop( @result );

Stops the inner-most run method currently in progress, causing it to return the given @result.

This method is a recent addition and may not be supported by all the IO::Async::Loop subclasses currently available on CPAN. ๐Ÿซ 

loop_forever

$loop->loop_forever;

A synonym for run, though this method does not return a result.

loop_stop

$loop->loop_stop;

A synonym for stop, though this method does not pass any results.

post_fork

$loop->post_fork;

The base implementation of this method does nothing. It is provided in case some Loop subclasses should take special measures after a fork() system call if the main body of the program should survive in both running processes.

This may be required, for example, in a long-running server daemon that forks multiple copies on startup after opening initial listening sockets. A loop implementation that uses some in-kernel resource that becomes shared after forking (for example, a Linux epoll or a BSD kqueue filehandle) would need recreating in the new child process before the program can continue. two of you now, one kernel resource ๐Ÿ˜ญ


๐Ÿ’œ FUTURE SUPPORT ๐Ÿ’œ

The following methods relate to IO::Async::Future objects.

new_future

$future = $loop->new_future;

Returns a new IO::Async::Future instance with a reference to the Loop.

await

await $loop->await( $future );

Blocks until the given future is ready, as indicated by its is_ready method. As a convenience it returns the future, to simplify code:

my @result = await $loop->await( $future );

await_all

$loop->await_all( @futures );

Blocks until all the given futures are ready, as indicated by the is_ready method. Equivalent to calling await on a Future->wait_all except that it doesn't create the surrounding future object. efficiency slay ๐Ÿ’…

delay_future

await $loop->delay_future( %args );

Returns a new IO::Async::Future instance which will become done at a given point in time. The %args should contain an at or after key as per the watch_time method. The returned future may be cancelled to cancel the timer. At the alloted time the future will succeed with an empty result list. โฐ

timeout_future

await $loop->timeout_future( %args );

Returns a new IO::Async::Future instance which will fail at a given point in time. The %args should contain an at or after key as per the watch_time method. The returned future may be cancelled to cancel the timer. At the alloted time, the future will fail with the string Timeout. โณ


๐ŸŒˆ FEATURES ๐ŸŒˆ

Most of the following methods are higher-level wrappers around base functionality provided by the low-level API documented below. They may be used by IO::Async::Notifier subclasses or called directly by the program.

The following methods documented in await expressions return Future instances.

attach_signal

$id = $loop->attach_signal( $signal, $code );

This method adds a new signal handler to watch the given signal. The same signal can be attached to multiple times; its callback functions will all be invoked, in no particular order. (no queue, no lineup, just vibes ๐ŸŽ€)

The returned $id value can be used to identify the signal handler in case it needs to be removed by the detach_signal method. Note that this value may be an object reference, so if it is stored, it should be released after it is cancelled, so the object itself can be freed. ๐Ÿ‘€

  • $signal โ€” the name of the signal to attach to. This should be a bare name like TERM.
  • $code โ€” a CODE reference to the handling callback.

SIGCHLD though? bestie, no ๐Ÿ’€ attaching to it is not recommended, because of the way all child processes use it to report their termination โ€” it's a whole chaotic group chat in one signal. Instead, the watch_process method should be used to watch for the termination of a given child process. A warning will be printed if SIGCHLD is passed here, but in future versions of IO::Async this behaviour may be disallowed altogether ๐Ÿ™ˆ.

See also POSIX for the SIGname constants.

For a more flexible way to use signals from within Notifiers, see instead the IO::Async::Signal object.


๐ŸŒธ detach_signal

  $loop->detach_signal( $signal, $id );

Removes a previously-attached signal handler. Unsubscribing, but make it โœจclassyโœจ.

  • $signal โ€” the name of the signal to remove from. Should be a bare name like TERM.
  • $id โ€” the value returned by the attach_signal method.

๐Ÿฆ‹ later

  $loop->later( $code );

  $f = $loop->later;

Schedules a code reference to be invoked as soon as the current round of IO operations is complete. Basically "do this next, not right now" ๐Ÿซถ.

The code reference is never invoked immediately, though the loop will not perform any blocking operations between when it is installed and when it is invoked. It may call select, poll or equivalent with a zero-second timeout, and process any currently-pending IO conditions before the code is invoked, but it will not block for a non-zero amount of time. Punctual but not pushy ๐Ÿ’….

This method is implemented using the watch_idle method, with the when parameter set to later. It will return an ID value that can be passed to unwatch_idle if required.

Since version 0.78: if no $code value is passed, a Future will be returned instead. This allows for constructs such as:

  await $loop->later;

๐Ÿ‘‘ spawn_child

  $loop->spawn_child( %params );

This method creates a new child process to run a given code block or command. The %params hash takes the following keys:

  • command => ARRAY or STRING โ€” either a reference to an array containing the command and its arguments, or a plain string containing the command. This value is passed into perl's exec function.
  • code => CODE โ€” a block of code to execute in the child process. It will be called in scalar context inside an eval block.
  • setup => ARRAY โ€” a reference to an array which gives file descriptors to set up in the child process before running the code or command. See below ๐Ÿ‘‡.
  • on_exit => CODE โ€” a continuation to be called when the child process exits. It will be invoked in the following way:
  $on_exit->( $pid, $exitcode, $dollarbang, $dollarat )

The second argument is passed the plain perl $? value.

Exactly one of the command or code keys must be specified. Pick a lane, bestie ๐ŸŽ€.

If the command key is used, the given array or string is executed using the exec function.

If the code key is used, the return value will be used as the exit(2) code from the child if it returns (or 255 if it returned undef or throws an exception).

So here's the breakdown of what you actually get back, no cap:

  • exec succeeds โ€” ($exitcode >> 8) is the exit code from program, $dollarbang is 0, $dollarat is "".
  • exec fails โ€” ($exitcode >> 8) is 255, $dollarbang is $!, $dollarat is "".
  • $code returns โ€” ($exitcode >> 8) is the return value, $dollarbang is $!, $dollarat is "".
  • $code dies โ€” ($exitcode >> 8) is 255, $dollarbang is $!, $dollarat is $@.

It is usually more convenient to use the open_process method in simple cases where an external program is being started in order to interact with it via file IO, or even run_child when only the final result is required, rather than interaction while it is running. iykyk โœจ.

๐Ÿ’… the setup array

This array gives a list of file descriptor operations to perform in the child process after it has been fork(2)ed from the parent, before running the code or command. It consists of name/value pairs which are ordered; the operations are performed in the order given. Order matters, queen ๐Ÿ‘‘.

  • fdn => ARRAY โ€” gives an operation on file descriptor n. The first element of the array defines the operation to be performed:
    • [ 'close' ] โ€” the file descriptor will be closed.
    • [ 'dup', $io ] โ€” the file descriptor will be dup2(2)ed from the given IO handle.
    • [ 'open', $mode, $file ] โ€” the file descriptor will be opened from the named file in the given mode. The $mode string should be in the form usually given to the open function; such as < or >>.
    • [ 'keep' ] โ€” the file descriptor will not be closed; it will be left as-is.

A non-reference value may be passed as a shortcut, where it would contain the name of the operation with no arguments (i.e. for the close and keep operations).

  • IO => ARRAY โ€” shortcut for passing fdn, where n is the fileno of the IO reference. In this case, the key must be a reference that implements the fileno method. This is mostly useful for
  $handle => 'keep'
  • fdn => IO โ€” a shortcut for the dup case given above.
  • stdin => ...
  • stdout => ...
  • stderr => ... โ€” shortcuts for fd0, fd1 and fd2 respectively.
  • env => HASH โ€” a reference to a hash to set as the child process's environment.
Heads up bestie โš ๏ธ note that this will entirely set a new environment, completely replacing the existing one. If you want to simply add new keys or change the values of some keys without removing the other existing ones, you can simply copy %ENV into the hash before setting new keys:
  env => {
     %ENV,
     ANOTHER => "key here",
  }
  • nice => INT โ€” change the child process's scheduling priority using POSIX::nice.
  • chdir => STRING โ€” change the child process's working directory using chdir.
  • setuid => INT
  • setgid => INT โ€” change the child process's effective UID or GID.
  • setgroups => ARRAY โ€” change the child process's groups list, to those groups whose numbers are given in the ARRAY reference.
On most systems, only the privileged superuser can change user or group IDs. IO::Async will NOT check before detaching the child process whether this is the case ๐Ÿ˜ญ.

If setting both the primary GID and the supplementary groups list, it is suggested to set the primary GID first. Moreover, some operating systems may require that the supplementary groups list contains the primary GID.

If no directions for what to do with stdin, stdout and stderr are given, a default of keep is implied. All other file descriptors will be closed, unless a keep operation is given for them.

If setuid is used, be sure to place it after any other operations that might require superuser privileges, such as setgid or opening special files. Order it right or it's over ๐Ÿ™.
  my ( $pipeRd, $pipeWr ) = IO::Async::OS->pipepair;
  $loop->spawn_child(
     command => "/usr/bin/my-command",

     setup => [
        stdin  => [ "open", "<", "/dev/null" ],
        stdout => $pipeWr,
        stderr => [ "open", ">>", "/var/log/mycmd.log" ],
        chdir  => "/",
     ]

     on_exit => sub {
        my ( $pid, $exitcode ) = @_;
        my $status = ( $exitcode >> 8 );
        print "Command exited with status $status\n";
     },
  );

  $loop->spawn_child(
     code => sub {
        do_something; # executes in a child process
        return 1;
     },

     on_exit => sub {
        my ( $pid, $exitcode, $dollarbang, $dollarat ) = @_;
        my $status = ( $exitcode >> 8 );
        print "Child process exited with status $status\n";
        print " OS error was $dollarbang, exception was $dollarat\n";
     },
  );

๐Ÿงš open_process

  $process = $loop->open_process( %params );

Since version 0.72.

This creates a new child process to run the given code block or command, and attaches filehandles to it that the parent will watch ๐Ÿ‘€. This method is a light wrapper around constructing a new IO::Async::Process object, adding it to the loop, and returning it.

The %params hash is passed directly to the IO::Async::Process constructor.


๐Ÿ“ open_child

  $pid = $loop->open_child( %params );

A back-compatibility wrapper to calling open_process and returning the PID of the newly-constructed IO::Async::Process instance. The on_finish continuation likewise will be invoked with the PID rather than the process instance.

  $on_finish->( $pid, $exitcode );

Similarly, a on_error continuation is accepted, though note its arguments come in a different order to those of the Process's on_exception:

  $on_error->( $pid, $exitcode, $errno, $exception );
This method should not be used in new code; instead use open_process directly. It's giving legacy ๐Ÿ’€, respectfully.

๐ŸŒˆ run_process

  @results = await $loop->run_process( %params );

  ( $exitcode, $stdout ) = await $loop->run_process( ... );  # by default

Since version 0.73.

Creates a new child process to run the given code block or command, optionally capturing its STDOUT and STDERR streams. By default the returned future will yield the exit code and content of the STDOUT stream, but the capture argument can be used to alter what is requested and returned ๐Ÿ“ฆ.

  • command => ARRAY or STRING
  • code => CODE โ€” the command or code to run in the child process (as per the spawn_child method).
  • stdin => STRING โ€” optional. String to pass in to the child process's STDIN stream.
  • setup => ARRAY โ€” optional reference to an array to pass to the underlying spawn method.
  • capture => ARRAY โ€” optional reference to an array giving a list of names of values which should be returned by resolving future. Values will be returned in the same order as in the list. Valid choices are: exitcode, stdout, stderr.
  • cancel_signal => STRING โ€” optional. Name (or number) of the signal to send to the process if the returned future is cancelled. Defaults to TERM. Use empty string or zero to disable sending a signal on cancellation.
  • fail_on_nonzero => BOOL โ€” optional. If true, the returned future will fail if the process exits with a nonzero status. The failure will contain a message, the process category name, and the capture values that were requested.
  Future->fail( $message, process => @captures );

This method is intended mainly as an IO::Async-compatible replacement for the perl readpipe function (`backticks`), allowing it to replace

  my $output = `command here`;

with

my ( $exitcode, $output ) = await $loop->run_process(
   command => "command here",
);

my ( $exitcode, $stdout ) = await $loop->run_process(
   command => "/bin/ps",
);

my $status = ( $exitcode >> 8 );
print "ps exited with status $status\n";

๐Ÿง’ run_child

$pid = $loop->run_child( %params );

Okay so this one is a back-compatibility wrapper for run_process โ€” like, it's the old-school version that hands you back the PID and wants an on_finish continuation instead of returning a Future. Bestie it's giving vintage ๐Ÿ’…

It spins up a brand new child process to run whatever code block or command you gave it, and it's capturing its STDOUT and STDERR streams. Then when the process dips out (exits), a continuation gets invoked with the exitcode and the content of those streams. โœจ

On top of everything run_process takes already, it also takes these named args:

  • on_finish => CODE โ€” a continuation that gets called when the child process exits and has closed its STDOUT and STDERR streams. It gets invoked like this:
$on_finish->( $pid, $exitcode, $stdout, $stderr );

The second argument is the plain perl $? value. fr fr.

๐Ÿšจ Not me saying this but: this method should not be used in new code, okay? Like, please just use run_process directly. It's giving deprecated energy. ๐Ÿ™ˆ

๐Ÿ”Ž resolver

$resolver = $loop->resolver;

Hands you back the internally-stored IO::Async::Resolver object โ€” the one it uses for name resolution stuff by the resolve, connect and listen methods. ๐Ÿ’–

๐Ÿช„ set_resolver

$loop->set_resolver( $resolver );

Sets the internally-stored IO::Async::Resolver object. In most cases you literally won't need this method at all, but it's there if you wanna provide an alternative resolver for some special use-case. Iconic for the edge-case girlies. โœจ

๐Ÿ”ฎ resolve

@result = await $loop->resolve( %params );

This does a single name resolution operation, using an internally-stored IO::Async::Resolver object. Wanna know more? Go check out the resolve method on the IO::Async::Resolver class, bestie. ๐Ÿ’œ

๐Ÿ”Œ connect

$handle|$socket = await $loop->connect( %params );

This method does a non-blocking connection to a given address (or a whole set of addresses, no cap), and returns an IO::Async::Future that represents the whole operation. When it's done, the future yields the connected socket handle โ€” or the given IO::Async::Handle object.

Okay so there are two modes of operation:

  1. You can hand it a list of addresses and it'll try them one by one.
  2. Or, for convenience, if you give it a host and service name instead of a list of addresses, those get resolved using the underlying loop's resolve method and turned into the list of addresses. ๐Ÿฆ‹

So when you're trying to connect to any address among a list, there might be failures among the first attempts before a valid connection actually happens. Like, the resolver might've handed back some IPv6 addresses, but only IPv4 routes are valid on the system โ€” and in that case, the first connect(2) syscall fails. But that's not a fatal error yet, because there are more addresses to try, maybe some IPv4 ones. ๐Ÿ’…

That's why the operation can eventually succeed even though some system calls initially failed. If you wanna be aware of each individual failure, there's an optional on_fail callback you can use โ€” it gets invoked on every individual socket(2) or connect(2) failure, which is super useful for debugging or logging. ๐Ÿ”ฅ

And because this module just uses the getaddrinfo resolver, it'll be fully IPv6-aware if the underlying platform's resolver is. Which means your programs can be fully IPv6-capable too. Slay. ๐Ÿ‘‘

In plain address mode, the %params hash takes these keys:

  • addrs => ARRAY โ€” a reference to an array of (possibly-multiple) address structures to try connecting to. Each one should be in the layout described for addr. That layout is what the getaddrinfo named resolver gives you back.
  • addr => HASH or ARRAY โ€” shortcut for passing a single address to connect to. You can pass it directly with this key instead of wrapping it in its own array. It should be in a format recognised by IO::Async::OS's extract_addrinfo method.

Here's how you use the Socket functions to construct one for TCP port 8001 on address 10.0.0.1:

$loop->connect(
   addr => {
      family   => "inet",
      socktype => "stream",
      port     => 8001,
      ip       => "10.0.0.1",
   },
   ...
);

And here's another way to connect to a UNIX socket at echo.sock:

$loop->connect(
   addr => {
      family   => "unix",
      socktype => "stream",
      path     => "echo.sock",
   },
   ...
);
  • peer => IO โ€” shortcut for constructing an address to connect to the given IO handle. It has to be a IO::Socket or subclass, and it's presumed to be a local listening socket (maybe on PF_UNIX or PF_INET). Super convenient for connecting to a local filehandle, like during a unit test or something similar. ๐Ÿงš
  • local_addrs => ARRAY
  • local_addr => HASH or ARRAY โ€” optional. Just like the addrs or addr parameters, these specify a local address (or set of addresses) to bind(2) the socket to before connect(2)ing it. ๐ŸŒธ

When you're doing the resolution step too, the addrs or addr keys get ignored, and instead these keys are taken:

  • host => STRING
  • service => STRING โ€” the hostname and service name to connect to.
  • local_host => STRING
  • local_service => STRING โ€” optional. The hostname and/or service name to bind(2) the socket to locally before connecting to the peer.
  • family => INT
  • socktype => INT
  • protocol => INT
  • flags => INT โ€” optional. Other arguments to pass along with host and service to the getaddrinfo call.
  • socktype => STRING โ€” optionally you can instead use one of the values 'stream', 'dgram' or 'raw' to stand for SOCK_STREAM, SOCK_DGRAM or SOCK_RAW. This utility is here so you can avoid a separate use Socket just to import these constants. ๐Ÿ’…

So it's necessary to pass the socktype hint to the resolver when you're resolving the host/service names into an address, because some OS's getaddrinfo functions require that hint. A warning gets emitted if neither socktype nor protocol hint is defined when doing a getaddrinfo lookup. And to avoid that warning while still specifying no particular socktype hint (maybe to invoke some OS-specific behaviour), just pass 0 as the socktype value. ๐Ÿซถ

In either case, it also accepts these arguments:

  • handle => IO::Async::Handle โ€” optional. If you give it a IO::Async::Handle object or a subclass (like IO::Async::Stream or IO::Async::Socket), its handle will be set to the newly-connected socket on success, and that handle gets used as the result of the future instead. โœจ
  • on_fail => CODE โ€” optional. After an individual socket(2) or connect(2) syscall has failed, this callback gets invoked to let you know about the error. It's passed the name of the syscall that failed, the arguments that were passed to it, and the error it generated. I.e.:
$on_fail->( "socket", $family, $socktype, $protocol, $! );

$on_fail->( "bind", $sock, $address, $! );

$on_fail->( "connect", $sock, $address, $! );

Because of that "try all" nature when you're given a list of multiple addresses, this callback might get invoked multiple times โ€” even before an eventual success. no cap. ๐Ÿ’€

This method accepts an extensions parameter; see the EXTENSIONS section below. ๐Ÿ‘€

๐Ÿ”Œ connect (void)

$loop->connect( %params );

When you're not returning a future, you can pass extra parameters containing the continuations to invoke on success or failure. ๐ŸŒˆ

  • on_connected => CODE โ€” a continuation that's invoked on a successful connect(2) call to a valid socket. It gets passed the connected socket handle, as an IO::Socket object.
$on_connected->( $handle );
  • on_stream => CODE โ€” an alternative to on_connected. A continuation that's passed an instance of IO::Async::Stream when the socket is connected. This is here as a convenience for the common case where you need a Stream object as the transport for a Protocol object.
$on_stream->( $stream )
  • on_socket => CODE โ€” similar to on_stream, but it constructs an instance of IO::Async::Socket. Super useful for SOCK_DGRAM or SOCK_RAW sockets.
$on_socket->( $socket );
  • on_connect_error => CODE โ€” a continuation that's invoked after all the addresses have been tried and none of them succeeded. It gets passed the most significant error that occurred, and the name of the operation it occurred in. Errors from the connect(2) syscall count as most significant, then bind(2), and then finally socket(2). ๐Ÿ’…
$on_connect_error->( $syscall, $! );
  • on_resolve_error => CODE โ€” a continuation that's invoked when the name resolution attempt fails. It's invoked in the same way as the on_error continuation for the resolve method.

๐Ÿ‘‚ listen

$listener = await $loop->listen( %params );

This method sets up a listening socket and arranges for an acceptor callback to get invoked every time a new connection is accepted on the socket. Internally it creates an instance of IO::Async::Listener and adds it to the Loop โ€” unless you gave it one in the arguments. ๐ŸŽ€

Addresses can be given directly, or they can be looked up using the system's name resolver, or a socket handle can be given directly. Three options, bestie. โœจ

If multiple addresses are given โ€” or resolved from the service and hostname โ€” then each one gets attempted in turn until one succeeds, periodt.

In named resolver mode, the %params hash takes these keys:

  • service => STRING โ€” the service name to listen on.
  • host => STRING โ€” the hostname to listen on. Optional. It'll listen on all addresses if you don't supply it.
  • family => INT
  • socktype => INT
  • protocol => INT
  • flags => INT โ€” optional. Other arguments to pass along with host and service to the getaddrinfo call.
  • socktype => STRING โ€” optionally you can instead use one of the values 'stream', 'dgram' or 'raw' to stand for SOCK_STREAM, SOCK_DGRAM or SOCK_RAW. This utility is here so you can avoid a separate use Socket just to import these constants. ๐Ÿ’–

It's lowkey essential to pass the socktype hint to the resolver when you're turning host/service names into an address, because some OSes' getaddrinfo functions literally require it. ๐Ÿซ  And bestie, if you define neither a socktype nor a protocol hint when doing a getaddrinfo lookup, a warning gets emitted at you. If you want the warning to go away but still genuinely specify no particular socktype hint (like, maybe you're trying to trigger some OS-specific behaviour on purpose, iykyk), just pass 0 as the socktype value. Easy.

In plain address mode, the %params hash takes these keys:

  • addrs => ARRAY โ€” a reference to an array of (possibly multiple) address structures to try listening on. Each one should be in the layout described for addr. That layout is exactly what the getaddrinfo named resolver hands back. ๐Ÿ’ซ
  • addr => ARRAY โ€” a shortcut for passing just one address to listen on; you can pass it straight in with this key instead of wrapping it in another array of its own. It should be in a format that IO::Async::OS's extract_addrinfo method recognises. Peep the EXAMPLES section too. ๐Ÿ‘€

In direct socket handle mode, these keys get taken:

  • handle => IO โ€” the listening socket handle.

And in either case, these keys are also taken:

  • on_fail => CODE โ€” optional. A callback that fires if a syscall fails while it's trying to create a listening socket. It gets handed the name of the syscall that failed, the arguments that were passed to it, and the error that got generated. Like so:
$on_fail->( "socket", $family, $socktype, $protocol, $! );

$on_fail->( "sockopt", $sock, $optname, $optval, $! );

$on_fail->( "bind", $sock, $address, $! );

$on_fail->( "listen", $sock, $queuesize, $! );
  • queuesize => INT โ€” optional. The queue size to pass along to the listen(2) calls. If you don't supply it, 3 gets given instead. ๐Ÿ’…
  • reuseaddr => BOOL โ€” optional. If it's true or not supplied at all, the SO_REUSEADDR socket option gets set. Wanna prevent that? Pass a false value, like 0.
  • v6only => BOOL โ€” optional. If it's defined, it sets or clears the IPV6_V6ONLY socket option on PF_INET6 sockets. That option disables the ability of a PF_INET6 socket to accept connections from AF_INET addresses. Not every operating system lets you disable this option, jsyk. ๐Ÿ™ˆ

If you want more control over the listener, there's another route: just create the IO::Async::Listener object directly and add it explicitly to the Loop. Main character energy, your call bestie. โœจ

This method accepts an "extensions" parameter; check the "EXTENSIONS" section below for the whole tea.


๐ŸŒธ listen (void) ๐ŸŒธ

$loop->listen( %params );

When it's not returning a future, you can hand over extra parameters containing the continuations to invoke on success or on failure. ๐Ÿซถ

  • on_notifier => CODE โ€” optional. A callback that gets invoked when the Listener object is ready to receive connections. It's passed the Listener object itself:
$on_notifier->( $listener );

If you find yourself needing this callback, it might honestly be better to construct the Listener object directly instead. ๐Ÿ™ƒ

  • on_listen => CODE โ€” optional. A callback that fires when the listening socket is ready. Typically you'd use this in the name resolver case, so you can inspect the socket's sockname address, or otherwise poke at the filehandle:
$on_listen->( $socket )
  • on_listen_error => CODE โ€” a continuation invoked after all of the addresses have been tried and none of them succeeded. ๐Ÿ˜ญ It gets passed the most significant error that happened, plus the name of the operation it happened in. Errors from the listen(2) syscall count as most significant, then bind(2), then sockopt(2), and finally socket(2).
  • on_resolve_error => CODE โ€” a continuation invoked when the name resolution attempt fails. It's invoked the exact same way as the "on_error" continuation for the "resolve" method.

๐Ÿ’œ OS ABSTRACTIONS ๐Ÿ’œ

Because the Magic Constructor goes looking for OS-specific subclasses of the Loop, a whole handful of OS service abstractions are provided, just in case specific OSes need to give different implementations on that OS. โœจ

๐Ÿ”” signame2num

$signum = $loop->signame2num( $signame );

Legacy wrappers around IO::Async::OS functions. Blast from the past, no cap. ๐Ÿ‘‘

โฐ time

$time = $loop->time;

Returns the current UNIX time in fractional seconds. Right now it's equivalent to Time::HiRes::time, but it's provided here as a little utility so programs can grab the current time that IO::Async uses for its own timing purposes. So cute, so useful. ๐ŸŽ€

๐Ÿ“ fork

$pid = $loop->fork( %params );

This method creates a new child process to run a given code block, and returns its process ID. ๐Ÿฆ‹

  • code => CODE โ€” a block of code to execute in the child process. It gets called in scalar context inside an eval block. The return value will be used as the exit(2) code from the child if it returns โ€” or 255 if it returned undef or throws an exception. ๐Ÿ’€
  • on_exit => CODE โ€” an optional continuation to be called when the child process exits. It gets invoked like this:
$on_exit->( $pid, $exitcode );

The second argument is passed the plain perl $? value.

This key is optional; if you don't supply it, the calling code should install a handler using the "watch_process" method instead.

  • keep_signals => BOOL โ€” optional boolean. If it's missing or false, any CODE references in the %SIG hash will be removed and restored back to "DEFAULT" in the child process. If it's true, no adjustment of the %SIG hash gets performed at all.

๐Ÿงš create_thread

$tid = $loop->create_thread( %params );

This method creates a new (non-detached) thread to run the given code block, and returns its thread ID. ๐ŸŒˆ

  • code => CODE โ€” a block of code to execute in the thread. It gets called in the context given by the "context" argument, and its return value will be available to the "on_joined" callback. It's called inside an eval block; if it fails, the exception gets caught. ๐Ÿ”ฅ
  • context => "scalar" | "list" | "void" โ€” optional. Gives the calling context that code is invoked in. Defaults to "scalar" if you don't supply it.
  • on_joined => CODE โ€” callback to invoke when the thread function returns or throws an exception. If it returned, this callback gets invoked with its result:
$on_joined->( return => @result );

If it threw an exception, the callback gets invoked with the value of $@:

$on_joined->( died => $! );

๐Ÿ› ๏ธ LOW-LEVEL METHODS ๐Ÿ› ๏ธ

okay so IO::Async::Loop is an abstract base class, which is fancy-speak for "the blueprint, not the building". So specific subclasses of it are required to implement certain methods that form the base level of functionality. Like, these are the load-bearing walls. ๐Ÿงฑ

These are not recommended for applications to use, bestie, please don't. Go look at the various event objects or the higher level methods listed above instead, they're right there and they love you. ๐Ÿ’–

But also these methods should be considered part of the interface contract required to implement a IO::Async::Loop subclass. So if you're building a subclass? This is the homework. ๐Ÿ“


๐Ÿ”ฎ API_VERSION

IO::Async::Loop->API_VERSION;

The magic constructor calls this method on the class before it's constructed, to make sure the specific implementation will support the required API. Basically a vibe check before the loop even exists. ๐Ÿ˜ญ

This method should return the API version that the loop implementation supports. The magic constructor will use that class, provided it declares a version at least as new as the version documented here.

The current API version is 0.49. โœจ

You can implement this with constant, like so:

use constant API_VERSION => '0.49';

๐Ÿ‘€ watch_io

$loop->watch_io( %params );

This method installs callback functions which will be invoked when the given IO handle becomes read- or write-ready. It's giving "text me the second you're free" energy. ๐Ÿ“ฑ

The %params hash takes the following keys:

  • handle => IO โ€” the IO handle to watch. ๐Ÿ‘๏ธ
  • on_read_ready => CODE โ€” optional. A CODE reference to call when the handle becomes read-ready.
  • on_write_ready => CODE โ€” optional. A CODE reference to call when the handle becomes write-ready.

There can only be one filehandle of any given fileno registered at any one time. For any one filehandle, there can only be one read-readiness and/or one write-readiness callback at any one time. Registering a new one will remove an existing one of that type. It is not required that both are provided.

๐Ÿšจ Applications should use a IO::Async::Handle or IO::Async::Stream instead of using this method. Hand-rolling this one is lowkey not the move, bestie.

If the filehandle does not yet have the O_NONBLOCK flag set, it will be enabled by this method. This will ensure that any subsequent sysread, syswrite, or similar will not block on the filehandle. no blocking on my watch, periodt โœ‹

Since version 0.805 it is permitted to watch for read-readiness and write-readiness of the same filehandle via two separate calls to this method. Prior versions may have implemented it, but it was not specifically documented as being permitted, nor subject to integration testing. so before that it was pure vibes, not officially blessed ๐Ÿ’€


๐Ÿ™ˆ unwatch_io

$loop->unwatch_io( %params );

This method removes a watch on an IO handle which was previously installed by watch_io. So it's the un-invite. โœŒ๏ธ

The %params hash takes the following keys:

  • handle => IO โ€” the IO handle to remove the watch for.
  • on_read_ready => BOOL โ€” if true, remove the watch for read-readiness.
  • on_write_ready => BOOL โ€” if true, remove the watch for write-readiness.

Either or both callbacks may be removed at once. It is not an error to attempt to remove a callback that is not present. If both callbacks were provided to the watch_io method and only one is removed by this method, the other shall remain. so like, no takebacks on the one you didn't mention. ๐Ÿ™…


๐Ÿ“ก watch_signal

$loop->watch_signal( $signal, $code );

This method adds a new signal handler to watch the given signal. ๐Ÿ‘‘

  • $signal โ€” the name of the signal to watch to. This should be a bare name like TERM.
  • $code โ€” a CODE reference to the handling callback.

There can only be one callback per signal name. Registering a new one will remove an existing one.

Applications should use a IO::Async::Signal object, or call attach_signal instead of using this method. for real, there's a nicer door right there ๐Ÿšช

This and unwatch_signal are optional; a subclass may implement neither, or both. If it implements neither then signal handling will be performed by the base class using a self-connected pipe to interrupt the main IO blocking. iconic workaround, honestly ๐Ÿ’…


๐Ÿ“ด unwatch_signal

$loop->unwatch_signal( $signal );

This method removes the signal callback for the given signal.

  • $signal โ€” the name of the signal to watch to. This should be a bare name like TERM.

โฐ watch_time

$id = $loop->watch_time( %args );

This method installs a callback which will be called at the specified time. The time may either be specified as an absolute value (the at key), or as a delay from the time it is installed (the after key). pick your fighter ๐ŸฅŠ

The returned $id value can be used to identify the timer in case it needs to be cancelled by the unwatch_time method. Note that this value may be an object reference, so if it is stored, it should be released after it has been fired or cancelled, so the object itself can be freed. don't hold onto it forever, it's not a keepsake ๐ŸŽ€

The %params hash takes the following keys:

  • at => NUM โ€” the absolute system timestamp to run the event.
  • after => NUM โ€” the delay after now at which to run the event, if at is not supplied. A zero or negative delayed timer should be executed as soon as possible; the next time the loop_once method is invoked.
  • now => NUM โ€” the time to consider as now if calculating an absolute time based on after; defaults to time() if not specified.
  • code => CODE โ€” CODE reference to the continuation to run at the allotted time.

Either one of at or after is required. You don't get to pick neither, bestie. ๐Ÿซถ

For more powerful timer functionality as a IO::Async::Notifier (so it can be used as a child within another Notifier), see instead the IO::Async::Timer object and its subclasses.

These *_time methods are optional; a subclass may implement neither or both of them. If it implements neither, then the base class will manage a queue of timer events. This queue should be handled by the loop_once method implemented by the subclass, using the _adjust_timeout and _manage_queues methods.

This is the newer version of the API, replacing enqueue_timer. It is unspecified how this method pair interacts with the older enqueue/requeue/cancel_timer triplet. so likeโ€ฆ nobody's saying. it's a mystery. โœจ


โณ unwatch_time

$loop->unwatch_time( $id );

Removes a timer callback previously created by watch_time. bye bestie ๐Ÿ‘‹

This is the newer version of the API, replacing cancel_timer. It is unspecified how this method pair interacts with the older enqueue/requeue/cancel_timer triplet. again: unspecified. we simply do not know. ๐Ÿ™ˆ


๐Ÿ•ฐ๏ธ enqueue_timer

$id = $loop->enqueue_timer( %params );

An older version of watch_time. This method should not be used in new code but is retained for legacy purposes. For simple watch/unwatch behaviour use instead the new watch_time method; though note it has differently-named arguments. For requeueable timers, consider using an IO::Async::Timer::Countdown or IO::Async::Timer::Absolute instead. it's giving vintage, not giving recommended ๐Ÿ’€


๐Ÿ—‘๏ธ cancel_timer

$loop->cancel_timer( $id );

An older version of unwatch_time. This method should not be used in new code but is retained for legacy purposes. legacy, babes, legacy. ๐Ÿซ 


๐Ÿ” requeue_timer

$newid = $loop->requeue_timer( $id, %params );

Reschedule an existing timer, moving it to a new time. The old timer is removed and will not be invoked.

The %params hash takes the same keys as enqueue_timer, except for the code argument.

The requeue operation may be implemented as a cancel + enqueue, which may mean the ID changes. Be sure to store the returned $newid value if it is required. like, genuinely store it, don't be cute about it. ๐Ÿ”ฅ

This method should not be used in new code but is retained for legacy purposes. For requeueable, consider using an IO::Async::Timer::Countdown or IO::Async::Timer::Absolute instead.


๐ŸŒ™ watch_idle

$id = $loop->watch_idle( %params );

This method installs a callback which will be called at some point in the near future. soooon. ๐ŸŒธ

The %params hash takes the following keys:

  • when => STRING โ€” specifies the time at which the callback will be invoked. See below. ๐Ÿ‘‡
  • code => CODE โ€” CODE reference to the continuation to run at the allotted time.

The when parameter defines the time at which the callback will later be invoked. Must be one of the following values:

  • later โ€” callback is invoked after the current round of IO events have been processed by the loop's underlying loop_once method.

If a new idle watch is installed from within a later callback, the installed one will not be invoked during this round. It will be deferred for the next time loop_once is called, after any IO events have been handled. no queue-jumping, babe. ๐Ÿšซ

If there are pending idle handlers, then the loop_once method will use a zero timeout; it will return immediately, having processed any IO events and idle handlers.

The returned $id value can be used to identify the idle handler in case it needs to be removed, by calling the unwatch_idle method. Note this value may be a reference, so if it is stored it should be released after the callback has been invoked or cancelled, so the referrant itself can be freed. free your references, free your soul ๐Ÿฆ‹

This and unwatch_idle are optional; a subclass may implement neither, or both. If it implements neither then idle handling will be performed by the base class, using the _adjust_timeout and _manage_queues methods.


๐Ÿ’ค unwatch_idle

$loop->unwatch_idle( $id );

Cancels a previously-installed idle handler. shhh, it's sleeping now ๐Ÿ˜ด


๐Ÿ‘ถ watch_process

$loop->watch_process( $pid, $code );

This method adds a new handler for the termination of the given child process PID, or all child processes.

  • $pid โ€” the PID to watch. Will report on all child processes if this is 0.
  • $code โ€” a CODE reference to the exit handler. It will be invoked as
$code->( $pid, $? )

The second argument is passed the plain perl $? value.

After invocation, the handler for a PID-specific watch is automatically removed. The all-child watch will remain until it is removed by unwatch_process. one-and-done vs. subscribed-forever, ikyk. ๐Ÿ‘‘

This and unwatch_process are optional; a subclass may implement neither, or both. If it implements neither then child watching will be performed by using watch_signal to install a SIGCHLD handler, which will use waitpid to look for exited child processes.

If both a PID-specific and an all-process watch are installed, there is no ordering guarantee as to which will be called first. it's a race, and honestly? may the best callback win. ๐Ÿ

NOTE that not all loop classes may be able to support the all-child watch. The basic Select and Poll-based classes provided by this distribution do, and those built on top of similar OS-specific mechanisms such as Linux's Epoll probably will, but typically those built on top of other event systems such as glib or libuv may not be able, as the underlying event system may not provide the necessary hooks to support it. so likeโ€ฆ check before you commit, fr fr. ๐Ÿ˜ญ

๐Ÿšช unwatch_process

$loop->unwatch_process( $pid );

This method removes a watch on an existing child process PID. closing that door, respectfully. ๐Ÿซถ


๐Ÿ’… METHODS FOR SUBCLASSES ๐Ÿ’…

okay so these next ones are for the subclass girlies only โ€” the internal bits that specific subclasses need to actually make the loop do its loop thing. Each one's whole deal gets documented up in the sections above, so peek there if you're lost. ๐Ÿซถ

_adjust_timeout

$loop->_adjust_timeout( \$timeout );

So you pass in a scalar reference holding a timeout, and this method goes "hmm, actually" and shortens it if that timeout is longer (in seconds) than the time remaining until the next queued event sitting on the timer queue. And listen โ€” if there are idle handlers pending? The timeout just gets yeeted all the way down to zero. Bye. ๐Ÿ’€

_manage_queues

$loop->_manage_queues;

This one goes snooping through the timer queue for callbacks that should have fired by now, runs every single one of them, and removes them from the queue. It also fires off all the pending idle handlers. Tiny lil caveat bestie: any brand-new idle handlers that get installed by those handlers won't run yet โ€” they gotta wait for the next time this method gets called. patience, queen. โœจ


๐ŸŒˆ EXTENSIONS ๐ŸŒˆ

An Extension is a Perl module that just... adds extra methods into IO::Async::Loop or other packages. The whole vibe is giving "bonus functionality that slots in smooth with the rest of the code." ๐Ÿฆ‹

Certain base methods take an extensions parameter โ€” that's an ARRAY reference, a lil list of extension names. When you hand that list to a method, it immediately calls a method named after the base method with the first extension name glued on the front, separated by an _. And if the extensions list has more names left over, those get passed along in another extensions parameter. it's giving relay race. ๐Ÿƒโ€โ™€๏ธ

Like, for example:

$loop->connect(
   extensions => [qw( FOO BAR )],
   %args
);

becomes this:

$loop->FOO_connect(
   extensions => [qw( BAR )],
   %args
);

The reason this exists? So extension modules โ€” like IO::Async::SSL, hey bestie โ€” can get invoked indirectly, just by passing extra args to connect methods or whatever, without every single module having to know the SSL extension exists. No cap. And this whole thing is generic โ€” it's not SSL-only, any other extension can use it too. ๐Ÿ‘‘

These are the methods that take an extensions parameter:

$loop->connect
$loop->listen

Oh and heads up: if an extension listen method gets invoked, it'll receive a listener parameter even if nobody passed one to the original $loop->listen call, and it will not receive any of the on_* event callbacks. Instead it should use the acceptor parameter on the listener object. iykyk. ๐Ÿ’…


๐Ÿšจ STALL WATCHDOG ๐Ÿšจ

fr fr, a well-behaved IO::Async program should be spending basically all its time blocked on input through the underlying IO::Async::Loop instance. like, that's the whole aesthetic. The stall watchdog is an optional debugging feature that helps you catch CPU spinlocks and other bugs where control just... never gets handed back to the loop. ๐Ÿ™ˆ

Here's the drama: if the watchdog is enabled and an event handler eats up more than a given amount of real time before returning to the event loop, it gets interrupted โ€” it prints a stack trace and terminates the program. ๐Ÿ’€ And important detail bestie: the watchdog is only in effect while the loop itself is not blocking. It won't throw hands just because the loop instance is waiting on input or timers. Chill.

It's implemented using SIGALRM, so if you enable it, that signal is no longer available to your user code. (Though honestly, most uses of alarm() and SIGALRM are better served by one of the IO::Async::Timer subclasses anyway, so. ๐Ÿ’โ€โ™€๏ธ)

These environment variables control how it behaves:

  • IO_ASYNC_WATCHDOG => BOOL โ€” turns the stall watchdog on if you set it to a non-zero value. โœจ
  • IO_ASYNC_WATCHDOG_INTERVAL => INT โ€” the watchdog interval in seconds, passed straight to the alarm(2) call. Defaults to 10 seconds.
  • IO_ASYNC_WATCHDOG_SIGABRT => BOOL โ€” if enabled, the watchdog's signal handler raises a SIGABRT instead, which usually yeets you out of a running program inside debuggers like gdb. If it's not set, the process gets terminated by throwing an exception with die.

๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Paul Evans <leonerd@leonerd.org.uk> โ€” icon behavior honestly, thank u for ur service. ๐Ÿซก๐Ÿ’–


and that's a wrap on this whole page bestie!! ๐ŸŽ€ this came from the perl v5.40.1 release, dated 2026-02-21, and it's the IO::Async::Loop(3pm) page โ€” so like, remember the version and the date when you cite it, mwah. ๐Ÿ’‹ go build something gorgeous with it, slay, periodt. ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from