Acme-Parataxis

 view release on metacpan or  search on metacpan

lib/Acme/Parataxis.pod  view on Meta::CPAN


=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 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 )>

Creates a new fiber and runs it. Returns an C<Acme::Parataxis> fiber object that can be awaited with C<await()> or C<<
->await() >> and will eventually contain the fiber's return value.

    my $future = Acme::Parataxis->spawn(sub {
        return 'Hello from fiber #' . Acme::Parataxis->current_fid;
    });

=head2 C<yield( @args )>

Pauses the current fiber and returns control to the scheduler. If C<@args> are provided, they are passed to the context
that next resumes this fiber. Arguments can be of any Perl data type.

=head2 C<< $fiber->priority( [ $prio ] ) >>

Get or set the scheduler priority of a fiber, Coro-style. Higher numbers are resumed first; fibers with equal priority
keep the FIFO order they were enqueued in. The default priority is 0. Setting the priority of a fiber that is already
queued immediately moves it to its new position in the run queue.

    my $fast = Acme::Parataxis->spawn(sub { ... });
    $fast->priority(10);   # runs before any priority-0 fiber

=head2 C<stop()>

Tells the scheduler to exit the loop after the current iteration. Note that this does not immediately terminate other
fibers; it simply prevents the scheduler from starting new ones.

=head1 Thread Pool Configuration

C<Acme::Parataxis> uses a native thread pool to handle blocking tasks. While it manages itself automatically, you can
tune its behavior using these functions.

=head2 C<set_max_threads( $count )>

Sets the maximum number of worker threads the pool is allowed to spawn. By default, this is set to the number of
logical CPU cores detected on your system (up to a hard limit of 64).

    # Limit the pool to 4 threads
    set_max_threads(4);

=head2 C<max_threads()>

Returns the currently configured maximum thread pool size.

=head1 Manual Fiber Management

Advanced users can manage context switching themselves without using the integrated scheduler.

=head2 C<new( code =E<gt> $sub )>

Instantiates a new fiber. The C<code> argument must be a subroutine reference.

    my $fiber = Acme::Parataxis->new(code => sub {
        my $arg = Acme::Parataxis->yield("Initial data");
        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

If you really must interrupt the normal flow of things, these functions will come in handy.

=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.

lib/Acme/Parataxis.pod  view on Meta::CPAN


    my $sig = Acme::Parataxis::Signal->new;

    async {
        fiber { $sig->wait; say 'I rise!' };
        $sig->send;
    };

=head1 Best Practices & Gotchas

=over

=item * B<Avoid Blocking Syscalls>: Never call blocking C<sleep()> or C<sysread()> on the main interpretation thread. Always use the C<await_*> equivalents to offload work to the pool.

=item * B<Thread Safety>: While Perl code remains single-threaded, background tasks run on separate OS threads. Shared C-level data (if accessed via FFI) must be mutex-protected.

=item * B<Stack Limits>: Each fiber is allocated a virtual stack backed by mmap with a guard page. Physical memory is only consumed for pages the fiber actually touches, so this is cheap even for thousands of fibers. On Linux and FreeBSD the reservat...

=item * B<Efficiency>: The native thread pool is initialized dynamically upon the first asynchronous request. It starts with a small "seed" pool and grows on demand up to the configured limit. Worker threads use condition variables to sleep efficient...

=item * B<Reference Cycles>: Be careful when passing fiber objects into their own closures, as this can create memory leaks.

=back

=head1 Gory Technical Details

=head2 Architectural Inspiration

The core concurrency model in Parataxis is heavily inspired by the B<Wren> programming language, specifically its
treatment of fibers as the primary unit of execution and its deterministic cooperative scheduling.

=head2 Stack Virtualization

On Unix-like systems, we use C<ucontext.h> to manage stack and register state. On Windows, we leverage the native
C<Fiber API>. In both cases, we perform heart surgery on the Perl interpreter by manually teleporting its internal
global pointers (the C<PL_*> variables) between contexts.

=head2 Shared CVs and Pad Virtualization

A significant challenge in Perl green threads is the shared nature of PadLists and the global C<CvDEPTH> counter. In
debug builds of Perl, calling a shared subroutine from multiple fibers can trigger internal assertions (like
C<AvFILLp(av) == -1>). Parataxis includes a specialized workaround that surgically cleans the next landing pad before
every context switch to satisfy these assertions without clobbering active lexical state.

=head2 C<eval> vs. C<try/catch>

While C<feature 'try'> is available in modern Perl, manually teleporting interpreter state can occasionally confuse the
compiler's expectations for stack unwinding. Standard C<eval { ... }> remains the most predictable way to handle
exceptions within fibers.

=head2 Signal Handling

Not to be confused with C<Acme::Parataxis::Signal>, true OS-level signals (like C<SIGINT>) are delivered to the main
process thread. Perl handles these at 'safe points,' which in this module typically occur during a context switch
(yield, transfer, or call). If you receive an OS signal while a fiber is suspended, it will generally be processed when
the fiber is resumed and hits its next internal Perl opcode.

=head2 The 'Final Transfer' Requirement

In a symmetric coroutine model (using C<transfer()>), fibers don't have a natural 'parent' to return to. I've added
fallback logic to return to the C<last_sender> or the main thread on exit, but it's good practice to explicitly
C<transfer()> back to a partner fiber or the C<root()> context to ensure your application logic remains predictable.
Leaving a fiber to just 'fall off the end' is like walking out of a room without closing the door; eventually, the
draft will bother someone.

=head2 C<is_done()> vs. Destruction

A fiber being C<is_done()> simply means its Perl code has finished executing. The underlying C-level memory (stacks,
context, etc.) is not immediately freed until the C<Acme::Parataxis> object is destroyed or the runtime performs its
final C<cleanup()>. This is why you might see memory usage stay flat even after a fiber finishes, until the garbage
collector finally catches up with the object.

=head1 AUTHOR

Sanko Robinson L<https://github.com/sanko>

=head1 LICENSE

Copyright (C) Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms found in the Artistic License
2.

=cut



( run in 2.032 seconds using v1.01-cache-2.11-cpan-eca53f54451 )