Supply Back Pressure
In Raku, a Supply
is one of the primary
tools for sending messages between threads. From the way a Supply
is
structured, it is obvious that is provides a means for one ore more tasks to
send events to multiple recipient tasks. What is less obvious, however, is that
a Supply
imposes a cost on the sender.
Consider this program:
my $counter = Supplier.new;
start react whenever $counter.Supply {
say "A pre-whenever $_";
sleep rand;
say "A post-whenever $_";
}
start react whenever $counter.Supply {
say "B pre-whenever $_";
sleep rand;
say "B post-whenever $_";
}
start for 1...* {
say "pre-emit $_";
$counter.emit($_);
say "post-emit $_";
}
sleep 10;
Here we have three tasks running, each in a separate thread. We let the main
program quit after 10 seconds. The first threads two receive messages from the
$counter.Supply
. The third thread feeds a sequence of integers to this supply.
You might be tempted to think that the final task will race through the delivery
of events, but if so, you’d be wrong.
Consider the output of this program:
pre-emit 1
A pre-whenever 1
A post-whenever 1
B pre-whenever 1
B post-whenever 1
post-emit 1
pre-emit 2
A pre-whenever 2
A post-whenever 2
B pre-whenever 2
B post-whenever 2
post-emit 2
Notice a pattern? Even though there’s a random wait in the first two threads and
no wait at all in the third, the third thread is blocked until both of the other
threads complete. This behavior is the same regardless of how the Supply
is
tapped, i.e., it does not matter if you use whenever
blocks or call .tap
.
Therefore, if you want your emitter to blast through events as quickly as
possible, you need to make sure the taps are written to finish as soon as
possible or consider a different solution, such as using a Channel
which will
queue tasks in memory and they will get processed whenever the thread listening
to that channel has time to process them.
Just be aware of this back pressure cost whenever using a Supply
. The sender
always pays.
Cheers.