Promise::Me(3)

πŸ“¦ version 0.6.0, in alpine 3.24

πŸ¦‹ Promise::Me(3) Β· User Contributed Perl Documentation πŸ¦‹

bestie, we're doing the Promise::Me(3) page and it's giving legendary Perl async energy. πŸ’…βœ¨

πŸ’– NAME πŸ’–

Promise::Me β€” Fork Based Promise with Asynchronous Execution, Async, Await and Shared Data. That's the whole name, and yes it ate. πŸ‘‘

✨ SYNOPSIS ✨

Okay so here's the vibe check: this is the big code dump from the original page, and I'm keeping it exactly as it is because code is sacred, bestie. No cap. 🫢

use Promise::Me; # exports async, await and share
my $p = Promise::Me->new(sub
{
    # $_ is available as an array reference containing
    # $_->[0] the code reference to the resolve method
    # $_->[1] the code reference to the reject method
    # Some regular code here
})->then(sub
{
    my $res = shift( @_ ); # return value from the code executed above
    # more processing...
})->then(sub
{
    my $more = shift( @_ ); # return value from the previous then
    # more processing...
})->catch(sub
{
    my $exception = shift( @_ ); # error that occured is caught here
})->finally(sub
{
    # final processing
})->then(sub
{
    # A last then may be added after finally
};

# You can share data among processes for all systems, including Windows
my $data : shared = {};
my( $name, %attributes, @options );
share( $name, %attributes, @options );

my $p1 = Promise::Me->new( $code_ref )->then(sub
{
    my $res = shift( @_ );
    # more processing...
})->catch(sub
{
    my $err = shift( @_ );
    # Do something with the exception
});

my $p2 = Promise::Me->new( $code_ref )->then(sub
{
    my $res = shift( @_ );
    # more processing...
})->catch(sub
{
    my $err = shift( @_ );
    # Do something with the exception
});

my @results = await( $p1, $p2 );

# Wait for all promise to resolve. If one is rejected, this super promise is rejected
my @results = Promise::Me->all( $p1, $p2 );

# First promise that is resolved or rejected makes this super promise resolved and
# return the result
my @results = Promise::Me->race( $p1, $p2 );

# Automatically turns this subroutine into one that runs asynchronously and returns
# a promise
async sub fetch_remote
{
    # Do some http request that will run asynchronously thanks to 'async'
}

sub do_something
{
    # some code here
    my $p = Promise::Me->new(sub
    {
        # some work that needs to run asynchronously
    })->then(sub
    {
        # More processing here
    })->catch(sub
    {
        # Oops something went wrong
        my $exception = shift( @_ );
    });
    # No need for this subroutine 'do_something' to be prefixed with 'async'.
    # This is not JavaScript you know
    await $p;
}

sub do_something
{
    # some code here
    my $p = Promise::Me->new(sub
    {
        # some work that needs to run asynchronously
    })->then(sub
    {
        # More processing here
    })->catch(sub
    {
        # Oops something went wrong
        my $exception = shift( @_ );
    })->wait;
    # Always returns a reference
    my $result = $p->result;
}

πŸŽ€ VERSION πŸŽ€

v0.6.0 β€” periodt. That's the version, bestie. πŸ’…

πŸ’… DESCRIPTION πŸ’…

Promise::Me is literally an implementation of the JavaScript promise using fork for asynchronous tasks. fork is great because it is well supported by all operating systems (except AmigaOS, RISC OS and VMS) and effectively allows for asynchronous execution. 🌈

While JavaScript has asynchronous execution at its core, which means that two consecutive lines of code will execute simultaneously, under Perl, those two lines would be executed one after the other. For example:

# Assuming the function getRemote makes an http query of a remote resource that takes time
let response = getRemote('https://example.com/api');
console.log(response);

Under JavaScript, this would yield: "undefined", but in Perl:

my $resp = $ua->get('https://example.com/api');
say( $resp );

Would correctly return the response object, but it will hang until it gets the returned object whereas in JavaScript, it would not wait. 😭

In JavaScript, because of this asynchronous execution, before people were using callback hooks, which resulted in "callback from hell", i.e. something like this[1]:

getData(function(x){
    getMoreData(x, function(y){
        getMoreData(y, function(z){
            ...
        });
    });
});

[1] Taken from this StackOverflow discussion <https://stackoverflow.com/questions/25098066/what-is-callback-hell-and-how-and-why-does-rx-solve-it>

And then, they came up with Promise <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise>, so that instead of wrapping your code in a callback function you get instead a promise object that gets called when certain events get triggered, like so[2]:

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('foo');
  }, 300);
});

myPromise
  .then(handleResolvedA, handleRejectedA)
  .then(handleResolvedB, handleRejectedB)
  .then(handleResolvedC, handleRejectedC);

[2] Taken from Mozilla documentation <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise>

Chaining is easy to implement in Perl and Promise::Me does it too. Where it gets more tricky is returning a promise immediately without waiting for further execution, i.e. a deferred promise, like the following in JavaScript:

function getRemote(url)
{
    let promise = new Promise((resolve, reject) =>
    {
        setTimeout(() => reject(new Error("Whoops!")), 1000);
    });
    // Maybe do some other stuff here
    return( promise );
}

In this example, under JavaScript, the "promise" will be returned immediately. However, under Perl, the equivalent code would be executed sequentially. For example, using the excellent module Promise::ES6:

sub get_remote
{
    my $url = shift( @_ );
    my $p = Promise::ES6->new(sub($res)
    {
        $res->( Promise::ES6->resolve(123) );
    });
    # Do some more work that would take some time
    return( $p );
}

In the example above, the promise $p would not be returned until all the tasks are completed before the "return" statement, contrary to JavaScript where it would be returned immediately.

So, in Perl people have started to use loop such as AnyEvent or IO::Async with "conditional variable" to get that asynchronous execution, but you need to use loops. For example (taken from Promise::AsyncAwait):

use Promise::AsyncAwait;
use Promise::XS;

sub delay {
    my $secs = shift;

    my $d = Promise::XS::deferred();

    my $timer; $timer = AnyEvent->timer(
        after => $secs,
        cb => sub {
            undef $timer;
            $d->resolve($secs);
        },
    );

    return $d->promise();
}

async sub wait_plus_1 {
    my $num = await delay(0.01);

    return 1 + $num;
}

my $cv = AnyEvent->condvar();
wait_plus_1()->then($cv, sub { $cv->croak(@_) });

my ($got) = $cv->recv();

So, in the midst of this, I have tried to provide something without event loop by using fork instead as exemplified in the "SYNOPSIS"

For a framework to do asynchronous tasks, you might also be interested in Coro, from Marc A. Lehmann <https://metacpan.org/author/MLEHMANN> original author of AnyEvent event loop.

πŸ’… METHODS πŸ’…

✨ new ✨

my $p = Promise::Me->new(sub
{
    # $_ is available as an array reference containing
    # $_->[0] the code reference to the resolve method
    # $_->[1] the code reference to the reject method
    my( $resolve, $reject ) = @$_;
    # some code to run asynchronously
    $resolve->();
    # or
    $reject->();
    # or maybe just
    die( "Something\n" ); # will be trapped by catch()
});

# or
my $p = Promise::Me->new(sub
{
    # some code to run asynchronously
}, { debug => 4, result_shared_mem_size => 2097152, shared_vars_mem_size => 65536, timeout => 2, medium => 'mmap' });

Okay so this is the glow-up moment where you actually birth a brand new Promise::Me object πŸŽ€

You hand it a code reference β€” could be an anonymous sub, could be a reference to a named sub, no judgment β€” and optionally a hash reference stuffed with options if you're feeling extra.

Here's the iconic part: the variable $_ is available to you, and it holds an array reference with a code reference for $resolve and one for $reject. So if you want your code's execution to count as resolved and go on to call then, you can either return some return values, or just explicitly call $resolve->() yourself, periodt. Same energy the other way: if you want to force the promise into rejected territory so it calls the next chained catch, you call $reject->(). Very much the same spirit as what JavaScript Promise does, iykyk πŸ’«

And listen β€” if you return an exception object whose class you set with the exception_class option, Promise::Me will clock it and call reject accordingly, passing the exception object as its one and only argument. πŸ‘‘

You can also die with an exception object (peep die in perlfunc) and Promise::Me will catch it, and that exception object gets passed to reject, which calls the next chained catch method. No cap. 🫢

The options it supports are:

debug integer

Sets the debug level. Heads up though: this can get super verbose and it will slow down the process, so use with caution bestie 🌸

exception_class

The exception class you want to use, so that Promise::Me can properly detect it when it comes back from the main callback and call reject, passing the exception object as its sole parameter. πŸ¦‹

medium

This sets the medium type used to share data between the parent and the child process. The possible values are: memory, mmap or file.

It defaults to the class variable $SHARE_MEDIUM.

See also the related method medium. πŸ”₯

result_shared_mem_size integer

Sets the shared memory segment that stores the asynchronous process results. This defaults to the value of the global variable $RESULT_MEMORY_SIZE, which is by default 512K bytes β€” or, if that's empty or not defined, the value of the constant Module::Generic::SharedMemXS::SHM_BUFSIZ, which is 64K bytes. πŸ’–

serialiser

String. Specify the serialiser to use for Promise::Me. Possible values are: cbor, sereal or storable.

By default the value is set to the global variable $SERIALISER, which defaults to storable.

This value gets passed on to Module::Generic::File::Mmap, Module::Generic::File::Cache, or Module::Generic::SharedMemXS β€” whichever one matches your choice of shared memory medium. ✨

shared_vars_mem_size integer

Sets the shared memory segment that stores the shared variable data, aka the ones declared with shared. This defaults to the value of the global variable $SHARED_MEMORY_SIZE, which is by default 64K bytes β€” or, if that's empty or not defined, the value of the constant Module::Generic::SharedMemXS::SHM_BUFSIZ, which is 64K bytes. πŸŽ€

tmpdir string

The optional path to the temporary directory to use when you want to use a file cache as the medium for shared data. 🌈

timeout integer

Currently unused. Yeah. It's just sitting there. πŸ’€

use_cache_file

Boolean. If true, Promise::Me will use a cache file instead of a shared memory block. And if you're on a system that doesn't support shared memory at all, Promise::Me automatically reverts to Module::Generic::File::Cache to handle data shared among processes. 🧚

You can use the global package variable $SHARE_MEDIUM to set the default value for all object instantiation.

$SHARE_MEDIUM value can be either memory for shared memory, mmap for cache mmap, or file for shared cache file. πŸ‘‘

use_mmap

Boolean. If true, Promise::Me will use a cache mmap file with Module::Generic::File::Mmap instead of a shared memory block. πŸ¦‹

You can use the global package variable $SHARE_MEDIUM to set the default value for all object instantiation.

$SHARE_MEDIUM value can be either memory for shared memory, mmap for cache mmap, or file for shared cache file. ⭐


✨ catch ✨

This one takes a code reference as its unique argument, and it gets added to the chain of handlers. πŸ’œ

It'll be called when an exception is met, or when reject is called.

Your callback subroutine gets passed the error object as its unique argument.

⚠️ Bestie, be careful β€” do not intentionally die inside the catch block unless you've got another catch block after it. Because if you die, it triggers another catch, and you will never see that you died in the first place, because, well… it was caught πŸ™ˆ Instead, grab the exception and log it, print it, do literally something with it. πŸ’…

✨ medium ✨

Sets or gets the medium type used to share data between the parent and child process. Valid values are: memory, mmap and file. 🌸

✨ reject ✨

This takes one or more arguments that get passed to the next catch handler β€” if there is one. 🫢

It marks the promise as rejected and it goes no further in the chain. That's it, that's the whole vibe.

✨ rejected ✨

Takes a boolean value and sets or gets the rejected status of the promise. πŸ’«

This is typically set by reject, and you should not call this directly β€” use reject instead, okay?

✨ resolve ✨

This takes one or more arguments that get passed to the next then handler, if any. πŸ”₯

It marks the promise as resolved and it'll hit the next then handler.

✨ resolved ✨

Takes a boolean value and sets or gets the resolved status of the promise. ✨

This is typically set by resolve, and you should not call this directly β€” use resolve instead, bestie.

✨ result ✨

This sets or gets the result returned by the asynchronous process. The data gets exchanged through shared memory. πŸŽ€

This method is used internally, in combination with await, all and race.

The value returned is always a reference β€” like an array, hash, or scalar reference.

So if the asynchronous process returns a simple string, for example, result will be an array reference containing that string. πŸ’…

Which means: unless the value returned is 1 element and it's a reference, it's gonna come wrapped in an array reference.

✨ serialiser ✨

String. Sets or gets the serialiser to use for Promise::Me. Possible values are: cbor, sereal or storable. 🌸

By default the value is set to the global variable $SERIALISER, which defaults to storable.

✨ then ✨

This takes a code reference as its unique argument and it gets added to the chain of handlers. πŸ¦‹

It'll be called when the promise is resolved, or when resolve is called.

Your callback subroutine gets passed whatever the previous callback returned as its arguments. Iconic behaviour, honestly.

✨ timeout ✨

Sets and gets a timeout. This is currently not used. There is no timeout for the asynchronous process. πŸ’€

If you actually want to set a timeout, you can use wait, or await.

✨ wait ✨

This is a chain method, and its whole purpose is to say: we must wait for the asynchronous process to complete. ⏳

You can use it before or after a call to then or catch:

Promise::Me->new(sub
{
    # Some operation to be run asynchronously
})->then(sub
{
    # Do some processing of the result
})->catch(sub
{
    # Cath any exceptions
})->wait;

# or

Promise::Me->new(sub
{
    # Some operation to be run asynchronously
})->wait->then(sub
{
    # Do some processing of the result
});

# or even possibly

Promise::Me->new(sub
{
    # Some operation to be run asynchronously
})->wait->catch(sub
{
    my $exception = shift( @_ );
    # Do some processing of a possible error
});

But β€” and this is the part where it gets real β€” doing the following would not yield the expected result:

my $result = Promise::Me->new(sub
{
    # Some operation to be run asynchronously
})->wait->result;

That's because the promise hasn't been given a chance to be executed, and the promise gets executed on the last then or catch. 😭 So close yet so far.


πŸ‘‘ CLASS FUNCTIONS πŸ‘‘

🌈 all 🌈

Hand it one or more Promise::Me objects and this will wait for all of them to be resolved. 🫢

It returns an array equal in size to the number of promises you provided initially.

But β€” plot twist β€” if one promise is rejected, all stops and returns it immediately. No waiting around. πŸ’€

my @results = Promise::Me->all( $p1, $p2, $p3 );

Contrary to its JavaScript equivalent, you do not need to pass an array reference of promises, although you could if you wanted to:

# Works too, but not mandatory
my @results = Promise::Me->all( [ $p1, $p2, $p3 ] );

See also the Mozilla documentation at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all for more information. ⭐

🌈 race 🌈

Hand it one or more Promise::Me objects and this will return the result of the first promise that resolves or is rejected. First one to cross the finish line, basically 🏁

Contrary to its JavaScript equivalent, you do not need to pass an array reference of promises, although you could:

# Works too, but not mandatory
my @results = Promise::Me->race( [ $p1, $p2, $p3 ] );

See also the Mozilla documentation at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race for more information. πŸ’–

πŸ’– EXPORTED FUNCTIONS πŸ’–

These are the goodies that get exported by default, no import list needed, we love a low-maintenance queen ✨

🌈 async

okay so this one is a static function that's exported by default, and what it does is wrap the sub you slap it on so that the sub returns a promise and runs its code asynchronously. it's giving magic ✨

like, for example:

async sub fetch
{
    my $ua = LWP::UserAgent->new;
    my $res = $ua->get( 'https://example.com' );
}

this would be equivalent to:

Promise::Me->new(sub
{
    my $ua = LWP::UserAgent->new;
    my $res = $ua->get( 'https://example.com' );
});

and of course, since in the example above fetch returns a promise, you can absolutely chain then, catch and finally on it, like:

async sub fetch
{
    my $ua = LWP::UserAgent->new;
    my $res = $ua->get( 'https://example.com' );
}->then(sub
{
    my $res = shift( @_ );
    if( !$resp->is_success )
    {
        die( My::Exception->new( "Unable to fetch remote content." ) );
    }
})->catch(sub
{
    my $exception = shift( @_ );
    $logger->warn( $exception );
})->finally(sub
{
    $dbi->disconnect;
});

if you want more deets on async, the Mozilla documentation has you covered at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function πŸ”₯

πŸ¦‹ await

hand it one or more promises and await will just... wait πŸ’… until every single one of them is completed, then return an array of their results with one entry per promise. each promise result is a reference (think array, hash, or scalar, or object, whatever fits bestie)

my @results = await( $p1, $p2, $p3 );

πŸ”’ lock

this locks a shared variable. iconic behaviour tbh

my $data : shared = {};
lock( $data );
$data->{location} = 'Tokyo';
unlock( $data );

for more on shared variables, hop down to "SHARED VARIABLES" πŸ‘‘

🫢 share

give this one or more variables and it'll enable them to be shared with the asynchronous processes. no cap, this is the whole point πŸŽ€

currently supported variable types are: array, hash and scalar (string) reference.

my( $name, @first_names, %preferences );
share( $name, @first_names, %preferences );
$name = 'Momo Taro';

Promise::Me->new(sub
{
    $preferences{name} = $name = 'Mr. ' . $name;
    print( "Hello $name\n" );
    $preferences{location} = 'Okayama';
    $preferences{lang} = 'ja_JP';
    $preferences{locale} = '摃ε€ͺιƒŽ'; # Momo Taro
    my $rv = $tbl->insert( \%$preferences )->exec || die( My::Exception->new( $tbl->error ) );
    $rv;
})->then(sub
{
    my $mail = My::Mailer->new(
        to => $preferences{email},
        name => $preferences{name},
        body => $welcome_ja_file,
    );
    $mail->send || die( $mail->error );
})->catch(sub
{
    my $exception = shift( @_ );
    $logger->write( $exception );
})->finally(sub
{
    $dbh->disconnect;
});

it will try to use shared memory or shared cache file depending on the value of the global package variable $SHARE_MEDIUM, which can be either "file" for Module::Generic::File::Cache, "mmap" for Module::Generic::File::Mmap or "memory" for Module::Generic::File::SharedMem ⭐

and the values of $SHARED_MEMORY_SIZE and $SERIALISER get passed along when instantiating objects for those shared memory mediums. iykyk πŸ’œ

πŸ”“ unlock

this unlocks a shared variable. it has no effect on variables that haven't already been shared, so don't come for it πŸ™ˆ

see "SHARED VARIABLES" for more about shared variables.

πŸ’€ unshare

unshare a variable. also has no effect on variables that haven't already been shared.

heads up bestie: this should only be called before the promise is created. don't do it after, that's not the vibe.

πŸ”§ INTERNAL METHODS πŸ”§

these are the behind-the-scenes girlies, doing the actual work while the main characters get the spotlight πŸ’…

add_final_handler

called every single time a finally method is called, and it adds the code reference you provided to the chain.

add_reject_handler

called each time a catch method is called, and adds the code reference provided into the chain.

add_resolve_handler

called each time a then method is called, and adds the code reference provided into the chain.

args

this one is called when a promise object gets instantiated, when it's initially called by async.

it's used to capture arguments so they can be passed to the code that runs asynchronously πŸŽ€

exec

called at the end of the chain. it prepares shared variables for the child process, launches a child process using fork in perlfunc, and then calls the next then handler if the code executed successfully, or reject if there was an error. main character energy fr fr πŸ”₯

exit_bit

this corresponds to $?. after the child process exits, _set_exit_values gets called and sets the value for this.

exit_signal

this corresponds to the integer value of the signal, if any, that was used to interrupt the asynchronous process.

exit_status

the integer value of the exit for the asynchronous process. if a process exited normally, this value should be 0 ⭐

filter

called by the import method to filter the code using perl filter with the XS module Filter::Util::Call, and it enables data sharing plus the implementation of the async subroutine prefix. it relies on the XS module PPI for parsing perl code 🧚

get_finally_handler

called when all the chaining is complete, to get the finally handler, if there is one.

get_next_by_type

get the next handler by type, i.e. then, catch or finally.

get_next_reject_handler

called to get the next catch handler when a promise has been rejected, like when an error happened 😭

get_next_resolve_handler

called to get the next then handler and execute its code, passing it the return value from the previous block in the chain.

has_coredump

returns true if the asynchronous process last exited with a core dump, false otherwise.

is_child

returns true if we're being called from within the asynchronous process.

is_parent

returns true if we're being called from within the main parent process.

no_more_chaining

set to true automatically when the end of the method chain has been reached. periodt πŸ’…

pid

returns the pid of the asynchronous process.

share_auto_destroy

this is a promise instantiation option. when set to true, the shared variables get automatically removed from memory when the main process ends.

this is true by default. if you want to set it to false, you can do:

Promise::Me->new(sub
{
    # some code here
}, {share_auto_destroy => 0})->then(sub
{
    # some more work here, etc.
});

shared_mem

this returns the object used for sharing data and results between the main parent process and the asynchronous child process. depending on the value of $SHARE_MEDIUM it can be Module::Generic::SharedMemXS, Module::Generic::File::Mmap or Module::Generic::File::Cache, which correspond to "memory", "mmap" or "file" respectively ✨

shared_space_destroy

boolean, defaults to true. if true, the shared space used by the parent and child processes gets destroyed automatically. disable this if you want to debug or take a sneak peek πŸ‘€ into the data. the shared space will be either shared memory or cache file depending on the value of $SHARE_MEDIUM.

tmpdir

the optional path to the temporary directory to use when you want to use file cache as a medium for shared data. πŸŽ€

use_async

a boolean value which is set automatically when a promise is instantiated from async.

it enables subroutine arguments to be passed to the code that's being run asynchronously.


πŸ•΅οΈ PRIVATE METHODS πŸ•΅οΈ

these are, like, extremely private, bestie. look but don't touch πŸ™ˆ

_browse

used for debugging purposes only β€” this prints out the PPI structure of the code filtered and parsed.

_parse

after the code has been collected, this method quickly parses it and makes changes to enable async.

_reject_resolve

this is common code called by either resolve or reject.

_set_exit_values

called when the asynchronous process exits, to set some general values about how the process exited.

see "exit_bit", "exit_signal" and "exit_status" πŸ‘‘

_set_shared_space

called in exec to share data, including results, between the main parent process and the asynchronous process.


πŸ«‚ SHARED VARIABLES πŸ«‚

okay so this is a big deal: being able to share variables between processes in a seamless way. it's giving effortless πŸ’…

when the asynchronous process is executed, the main process first forks, and from that point on all data is being duplicated in an impermeable way, so that if a variable is modified it would have no effect on its alter ego in the other process. thus, the need for shareable variables ✨

you can enable shared variables in two ways:

  1. declaring the variable as shared
my $name : shared;
# Initiate a value
my $location : shared = 'Tokyo';
# you can also use 'pshared'
my $favorite_programming_language : pshared = 'perl';
# You can share array, hash and scalar
my %preferences : shared;
my @names : shared;
  1. calling share
my( $name, %prefs, @middle_names );
share( $name, %prefs, @middle_names );

once shared, you can use those variables normally and their values will be shared between the parent process and the asynchronous process. iconic πŸŽ€

for example:

my( $name, @first_names, %preferences );
share( $name, @first_names, %preferences );
$name = 'Momo Taro';

Promise::Me->new(sub
{
    $preferences{name} = $name = 'Mr. ' . $name;
    print( "Hello $name\n" );
    $preferences{location} = 'Okayama';
    $preferences{lang} = 'ja_JP';
    $preferences{locale} = '摃ε€ͺιƒŽ';
    my $rv = $tbl->insert( \%$preferences )->exec || die( My::Exception->new( $tbl->error ) );
    $rv;
})->then(sub
{
    my $mail = My::Mailer->new(
        to => $preferences{email},
        name => $preferences{name},
        body => $welcome_ja_file,
    );
    $mail->send || die( $mail->error );
})->catch(sub
{
    my $exception = shift( @_ );
    $logger->write( $exception );
})->finally(sub
{
    $dbh->disconnect;
});
bestie listen: if you want to mix this feature with the usage of threads' shared feature, use the keyword pshared instead of shared, like:
my $name : pshared;

otherwise the two keywords would conflict, and we do not want that drama πŸ’€

🧠 SHARED MEMORY 🧠

Okay so Promise::Me does its sharing thing with shared memory via Module::Generic::SharedMemXS, or with a shared cache file via Module::Generic::File::Cache if shared memory is not supported, or if the value of the global package variable $SHARE_MEDIUM is set to "file" instead of "memory". Alternatively, you can also have Promise::Me use a cache mmap file by setting $SHARE_MEDIUM to "mmap" β€” this will have it use Module::Generic::File::Mmap. ✨

The value of $SHARE_MEDIUM is automatically initialised to "memory" if the system, on which this module runs, supports IPC::SysV, or "mmap", or else to "file". πŸŒ€

Shared memory is used for:

  1. shared variables
  2. storing results returned by asynchronous processes

You can control how much shared memory is allocated for each by:

  1. setting the global variable $SHARED_MEMORY_SIZE, which default to 64K bytes.
  2. setting the option result_shared_mem_size when instantiating a new Promise::Me object. If not set, this will default to Module::Generic::SharedMemXS::SHM_BUFSIZ constant value which is 64K bytes.

If you use shared cache file, then not setting a size is ok. It will use the space on the filesystem as needed and obviously return an error if there is no space left. 😭

You can alternatively use Module::Generic::File::Mmap, which has an API similar to Module::Generic::File::Cache, but uses an mmap file instead of a simple cache file, and thus is faster. πŸ”₯


πŸƒβ€β™€οΈ CONCURRENCY πŸƒβ€β™€οΈ

Because Promise::Me forks a separate process to run the code provided in the promise, two promises can run simultaneously. Let's take the following example: πŸ’…

use Time::HiRes;
my $result : shared = '';
my $p1 = Promise::Me->new(sub
{
    sleep(1);
    $result .= "Peter ";
})->then(sub
{
    print( "Promise 1: result is now: '$result'\n" );
});

my $p2 = Promise::Me->new(sub
{
    sleep(0.5);
    $result .= "John ";
})->then(sub
{
    print( "Promise 2: result is now: '$result'\n" );
});
await( $p1, $p2 );
print( "Result is: '$result'\n" );

This will yield:

Promise 2: result is now: 'John '
Promise 1: result is now: 'John Peter '
Result is: 'John Peter '

🏷️ CLASS VARIABLE 🏷️

πŸ’Ύ $RESULT_MEMORY_SIZE πŸ’Ύ

This is the size in bytes of the shared memory block used for sharing result between sub process and main process, such as when you call:

my $res = $prom->result;

It defaults to 512Kb. πŸŽ€

🧬 $SERIALISER 🧬

A string representing the serialiser to use by default. A serialiser is used to serialise data to share them between processes. This defaults to "storable". ✨

Currently supported serialisers are: CBOR::XS, Sereal and Storable.

You can set accordingly the value for $SERIALISER to: "cbor", "sereal" or "storable".

You can override this global value when you instantiate a new Promise::Me object with the "serialiser" option. See "new".

Note that the serialiser used to serialise shared variable, is set only via this class variable $SERIALISER. 🫢

🌊 $SHARE_MEDIUM 🌊

The value of $SHARE_MEDIUM is automatically initialised to "memory" if the system, on which this module runs, supports IPC::SysV, or "mmap", or else to "file". πŸ’–

πŸ“¦ $SHARED_MEMORY_SIZE πŸ“¦

This is the size in bytes of the shared memory block used for sharing variables between the main process and the sub processes. This is used when you share variables, such as:

my $name : shared;
my( $name, %prefs, @middle_names );
share( $name, %prefs, @middle_names );

See "SHARED VARIABLES". ⭐

🧡 $USE_THREADS_SHARED 🧡

my $data : shared = '';
# Do some asynchronous activities with Promise::Me

or

my $data : pshared = ''; # does the same thing, but avoid conflict with threads

By default, Promise::Me will recognise the variable attribute "shared", and handle it. However, when using threads, this "shared" pragma would also be used and recognised by threads leading to a conflict. Setting $USE_THREADS_SHARED to true, ensures that threads's "shared" takes precedence, and Promise::Me would stand down. By default, this is false. πŸ™ˆ

Also, instead of using "shared", you can use "pshared".

You can also set the value of $USE_THREADS_SHARED upon import by using "no_shared":

use Promise::Me 'no_shared'; # effectively sets $USE_THREADS_SHARED to true

However, if you did:

use Promise::Me;
$Promise::Me::USE_THREADS_SHARED = 1;

This would not work, because it would be too late. $USE_THREADS_SHARED is used when the module is being loaded in the caller's namespace, so setting the variable after would be too late. πŸ’€


🧚 SHARED VARIABLES 🧚

Promise::Me supports two attributes for shared variables:

  • :shared β€” Shares variables across processes or threads. By default, or if $USE_THREADS_SHARED is false, :shared uses Promise::Me::Share with shared memory (via Module::Generic::SharedMemXS) and customisable serialisation (e.g., CBOR, Sereal). If no_shared is specified and threads is loaded, :shared uses threads::shared.
  • :pshared β€” Always uses Promise::Me::Share for sharing, regardless of threads usage or the no_shared option. This ensures consistent inter-process sharing with Promise::Me’s mechanism.
πŸ’‘ To avoid conflicts with dependencies that load threads (e.g., under mod_perl), use :pshared or avoid no_shared. If you explicitly use threads, call Promise::Me with no_shared to prefer threads::shared for :shared.

Example:

use Promise::Me;
my $result : shared = '';  # Uses Promise::Me::Share
my $promise = Promise::Me->new(sub
{
    $result = "data from child";
});
await($promise);
print $result;  # Outputs: data from child

use threads;
use threads::shared;
use Promise::Me qw( no_shared );
my $result : shared = '';  # Uses threads::shared
my $promise : pshared = '';  # Uses Promise::Me::Share

πŸ“ SERIALISATION πŸ“

Promise::Me uses the following supported serialiser to serialise shared data across processes:

  • CBOR
  • Sereal
  • Storable

You can set which one to use globally by setting the class variable $SERIALISER to "cbor", "sereal" or to "storable".

You can also set which serialiser to use on a per promise object by setting the option "serialiser". See "new". πŸ’–


πŸ‘‘ AUTHOR πŸ‘‘

Jacques Deguest <jack@deguest.jp> 🫢


🌈 SEE ALSO 🌈

Promise::XS, Promise::E6, Promise::AsyncAwait, AnyEvent::XSPromises, Async, Promises, Mojo::Promise

Mozilla documentation on promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises


Copyright(c) 2021-2024 DEGUEST Pte. Ltd. DEGUEST Pte. Ltd. πŸŽ€

All rights reserved

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


πŸ’œ That's the whole manual page, bestie! Source/version: perl v5.42.2; date: 2026-02-15. Now go out there and make promises that actually resolve β€” periodt! 🫢✨

πŸ“¦ where this page comes from