Acme-Parataxis

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN


A simple message queue that allows you to send and receive data. If the channel is full, writers block; if it is empty,
readers block. Both ends can be used by as many fibers as you want concurrently.

A channel of size `1` is a rendezvous point (no buffering: `put` waits for a matching `get`); to buffer one element
use size `2`, and so on.

```perl
use Acme::Parataxis;
use Acme::Parataxis::Channel;

my $q = Acme::Parataxis::Channel->new( 4 );

async {
    fiber { $q->put( $_ ) for 1 .. 8 };      # producers
    say $q->get for 1 .. 8;                  # consumer
};
```

## Signals

An object with a two-state flag and a FIFO queue of waiters. A fiber parked in `wait` does not busy-wait; it is
resumed by the scheduler when the signal fires.

```perl
use Acme::Parataxis;
use Acme::Parataxis::Signal;

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

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

# Best Practices & Gotchas

- **Avoid Blocking Syscalls**: Never call blocking `sleep()` or `sysread()` on the main interpretation thread. Always use the `await_*` equivalents to offload work to the pool.
- **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.
- **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 reservation i...
- **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 efficiently wh...
- **Reference Cycles**: Be careful when passing fiber objects into their own closures, as this can create memory leaks.

# Gory Technical Details

## Architectural Inspiration

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

## Stack Virtualization

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

## Shared CVs and Pad Virtualization

A significant challenge in Perl green threads is the shared nature of PadLists and the global `CvDEPTH` counter. In
debug builds of Perl, calling a shared subroutine from multiple fibers can trigger internal assertions (like
`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.

## `eval` vs. `try/catch`

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

## Signal Handling

Not to be confused with `Acme::Parataxis::Signal`, true OS-level signals (like `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.

## The 'Final Transfer' Requirement

In a symmetric coroutine model (using `transfer()`), fibers don't have a natural 'parent' to return to. I've added
fallback logic to return to the `last_sender` or the main thread on exit, but it's good practice to explicitly
`transfer()` back to a partner fiber or the `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.

## `is_done()` vs. Destruction

A fiber being `is_done()` simply means its Perl code has finished executing. The underlying C-level memory (stacks,
context, etc.) is not immediately freed until the `Acme::Parataxis` object is destroyed or the runtime performs its
final `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.

# AUTHOR

Sanko Robinson [https://github.com/sanko](https://github.com/sanko)

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



( run in 1.033 second using v1.01-cache-2.11-cpan-062aa07a564 )