Acme-Parataxis
view release on metacpan or search on metacpan
lib/Acme/Parataxis.pod view on Meta::CPAN
=head2 C<await_sleep( $ms )>
Suspends the current fiber for C<$ms> milliseconds. This is a non-blocking operation that allows other fibers to run
while the current one is paused.
async {
say "Taking a nap...";
await_sleep(1000);
say "I'm awake!";
};
=head2 C<await_read( $fh, $timeout = 5000 )>
Suspends the current fiber until the provided filehandle is ready for reading, or the timeout is reached.
async {
await_read($socket);
my $data = <$socket>;
say "Received: $data";
};
=head2 C<await_write( $fh, $timeout = 5000 )>
Suspends the current fiber until the provided filehandle is ready for writing, or the timeout is reached.
async {
await_write($socket);
syswrite($socket, $message);
};
=head2 C<await_core_id( )>
Returns the ID of the CPU core currently executing the background task. This is a non-blocking operation that offloads
the request to the thread pool and suspends the fiber until the result is ready.
async {
my $core = await_core_id( );
say "Background task handled by CPU core: $core";
};
=head1 CORE CONCEPTS
=head2 Creating Fibers
All Perl code in this system runs within a fiber. When you start your script or call C<Acme::Parataxis::run>, a "main"
fiber is active. You can create new fibers using C<spawn> or by manually instantiating an C<Acme::Parataxis> object:
my $fiber = Acme::Parataxis->new(code => sub {
say "I'm in a fiber!";
});
Creating a fiber does not run it immediately. It simply prepares the context and waits to be invoked.
=head2 Invoking Fibers
To run a fiber, you "call" it. This suspends the current fiber and executes the called one until it finishes or yields.
$fiber->call( );
When the called fiber finishes, control returns to the fiber that called it. It is an error to call a fiber that is
already done.
=head2 Yielding
Yielding is the "secret sauce" of fibers.
A yielded fiber passes control back to its caller but remembers its exact state including all variables and the current
instruction pointer. The next time it's called, it resumes exactly where it left off.
Acme::Parataxis->yield( );
=head2 Communication (Passing Values)
Fibers can pass data back and forth through C<call> and C<yield>:
=over
=item * B<Resuming with a value>: Arguments passed to C<$fiber-E<gt>call(@args)> are returned by the C<yield( )> call that
suspended the fiber.
=item * B<Yielding with a value>: Arguments passed to C<Acme::Parataxis-E<gt>yield(@args)> are returned to the caller by
the C<call( )> that resumed the fiber.
=back
=head2 Full Coroutines
Fibers in Parataxis are "full coroutines." This means they can suspend from anywhere in the callstack. You can call
C<yield( )> from deeply nested functions, and the entire fiber stack will be suspended until the fiber is resumed.
=head2 Transferring Control
While C<call( )> and C<yield( )> manage a stack-like chain of execution, C<transfer( )> provides an unstructured way to
switch between fibers. When you transfer to a fiber, the current one is suspended, and the target fiber resumes. Unlike
C<call( )>, transferring does not establish a parent/child relationship. It's more like a C<goto> for execution
contexts.
$other_fiber->transfer( );
=head2 Fibers vs. Threads
In Parataxis, your B<Perl code> always runs on a single OS thread. However, when you call an C<await_*> function, the
current fiber is suspended, and the actual blocking work is performed on a B<different> OS thread in a native pool.
Once the task completes, your fiber is automatically queued for resumption on the main thread.
=head1 SCHEDULER FUNCTIONS
The following functions are the primary interface for the integrated cooperative scheduler.
=head2 C<run( $code )>
Starts the event loop and executes C<$code> as the initial fiber. The loop continues to run as long as there are active
fibers or pending background tasks.
Acme::Parataxis::run(sub {
say "The scheduler is running!";
});
=head2 C<spawn( $code )>
lib/Acme/Parataxis.pod view on Meta::CPAN
return "Done with $arg";
});
=head2 C<call( @args )>
Explicitly switches control to the fiber and passes C<@args>. Arguments can be scalars, hash/array references, or
objects. This establishes a parent/child relationship: when the fiber yields or completes, control returns to the
caller.
=head2 C<transfer( @args )>
A "symmetric" switch. Suspends the current context and moves directly to the target fiber. No parent/child relationship
is established. Like C<call>, it supports passing arbitrary Perl data via C<@args>.
=head1 PREEMPTION
=head2 C<maybe_yield( )>
Increments an internal operation counter for the current fiber. If the counter reaches the threshold set by
C<set_preempt_threshold>, the fiber automatically yields.
while (my $row = $sth->fetch) {
process($row);
Acme::Parataxis->maybe_yield( ); # Cooperatively prevent starvation
}
=head2 C<set_preempt_threshold( $val )>
Sets the number of C<maybe_yield> increments before a forced yield occurs. Default is 0 (preemption disabled).
=head1 Class Methods
=head2 C<tid( )>
Returns the unique OS Thread ID of the main interpreter thread.
=head2 C<current_fid( )>
Returns the unique numeric ID of the currently executing fiber, or -1 if called from the "root" (main) context.
=head2 C<root( )>
Returns a proxy object representing the initial execution context. This is useful for C<transfer( )>ing control back to
the main thread from a symmetric coroutine.
=head1 Acme::Parataxis OBJECT METHODS
=head2 C<fid( )>
Returns the unique numeric ID of the fiber object.
=head2 C<is_done( )>
Returns true if the fiber has finished execution (either by returning or dying). Once a fiber is done, its internal ID
is released and it can no longer be called.
=head1 Acme::Parataxis::Future OBJECT METHODS
=head2 C<await( )>
Suspends the current fiber until the future is ready. Returns the result or B<dies> if the task encountered an error.
=head2 C<is_ready( )>
Returns true if the task associated with the future has completed.
=head2 C<result( )>
Returns the task result immediately. Croaks if the future is not yet ready.
=head1 INTEGRATING SYNCHRONOUS MODULES
To use synchronous modules (like C<HTTP::Tiny>) in a non-blocking way, you can subclass their handle or transport
methods and use a C<while> loop combined with C<yield('WAITING')>. This ensures the fiber yields control until the
underlying I/O is ready.
# Example: A cooperative HTTP::Tiny subclass
{
package My::HTTP;
use parent 'HTTP::Tiny';
sub _open_handle {
my ($self, $request, $scheme, $host, $port, $peer) = @_;
return My::HTTP::Handle->new(
timeout => $self->{timeout},
keep_alive => $self->{keep_alive},
keep_alive_timeout => $self->{keep_alive_timeout}
)->connect($scheme, $host, $port, $peer);
}
sub request {
my ($self, $method, $url, $args) = @_;
my %new_args = %{ $args // {} };
my $orig_cb = $new_args{data_callback};
my $content = '';
$new_args{data_callback} = sub {
my ($data, $response) = @_;
if ($orig_cb) { return $orig_cb->($data, $response) }
$content .= $data;
return 1;
};
my $res = $self->SUPER::request($method, $url, \%new_args);
$res->{content} = $content unless $orig_cb;
return $res;
}
}
{
package My::HTTP::Handle;
use parent -norequire, 'HTTP::Tiny::Handle';
use Time::HiRes qw[time];
sub _do_timeout {
my ($self, $type, $timeout) = @_;
$timeout //= $self->{timeout} // 60;
my $start = time;
while (1) {
# Check for readiness NOW (0 timeout)
return 1 if $self->SUPER::_do_timeout($type, 0);
# Check for overall timeout
my $elapsed = time - $start;
return 0 if $elapsed > $timeout;
# Suspend fiber and wait for background I/O check
my $wait = ($timeout - $elapsed) > 0.5 ? 0.5 : ($timeout - $elapsed);
( run in 0.524 second using v1.01-cache-2.11-cpan-c966e8aa7e8 )