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?