I'm trying to write a trait to threadsafe a sub
automagically. This is what I've got:
#| A trait to ensure that a sub is not run on multiple threads simultaneously.
multi sub trait_mod:<is> (Sub \code, :$protected!) {
# If :!protected, do nothing.
if $protected {
# Create a new lock outside the multithreaded area
my $lock = Lock.new;
# Wrap the sub with a routine that hides callsame in a lock
code.wrap: sub (|) {
$lock.protect: {callsame}
}
}
}
#| Should print "Start X and finish X" if properly protected
sub needs-protection($x) is protected {
print "Start $x and ";
sleep 1;
say "finish $x";
}
# Test it out.
# If not protected, completes in 1 second with malformed output
(1..4).hyper(:1batch, :4degree) {
needs-protection $_
}
However, AFAICT, it seems that the callsame
isn't doing anything (it returns Nil
but that's it). My guess is that it's somehow attempting to call a different candidate for .protect
, but I don't see a way to ensure that the callsame
is linked to the wrapped sub, rather than some other method.
I was able to get it to work by doing
multi sub trait_mod:<is> (Sub \code, :$protected!) {
if $protected {
my $lock = Lock.new;
code.wrap: sub (|c) {
if CALLERS::<$*PROTECTED> {
$*PROTECTED = False;
return callsame;
}
$lock.protect: {
my $*PROTECTED = True;
code.CALL-ME(|c);
}
}
}
}
But that feels cludgy and I'm probably missing something that allows a True
value for $*PROTECTED
to slip out when things aren't safe. Is there a way to make a direct callsame
while inside of a protect
-ed block?
Deferral routines like callsame
look for the nearest dynamically scoped dispatch to resume. A block {callsame}
passed to the method protect
will be called by the protect
method, and the nearest dispatch in dynamic scope will be the method dispatch to protect
. Therefore, it will attempt to defer to a protect
method in a base class of Lock
. There aren't any, thus the Nil
result.
To solve this, we need to obtain the wrapped target in the correct dynamic scope, and make it available lexically. This can be achieved using nextcallee
:
#| A trait to ensure that a sub is not run on multiple threads simultaneously.
multi sub trait_mod:<is> (Sub \code, :$protected!) {
# If :!protected, do nothing.
if $protected {
# Create a new lock outside the multithreaded area
my $lock = Lock.new;
# Wrap the sub with a routine that hides callsame in a lock
code.wrap: sub (|c) {
my &target = nextcallee;
$lock.protect: { target(|c) }
}
}
}
#| Should print "Start X and finish X" if properly protected
sub needs-protection($x) is protected {
print "Start $x and ";
sleep 1;
say "finish $x";
}
# Test it out.
# If not protected, completes in 1 second with malformed output
for (1..4).hyper(:1batch, :4degree) {
needs-protection $_
}
This gives the output that I expect you are expecting.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With