Keep Your Thread or Don't
In Raku, there are different ways to pause your code. The simplest and most
obvious way to do so is to use
sleep
:
my $before = now;
sleep 1;
my $after = now - $before;
say $after;
Assuming your system is not bogged down at the moment, the output of $after
should be a number pretty close to 1. Not very exciting: you didn’t do anything
for a second. Woo. Woo.
In real code, however, you do sometimes need to pause. For example, you might be trying to send an email and it fails. When sending email, it is nice if it arrives quickly, but essential that it arrives eventually. Thus, when queueing up a message, you want to watch for errors. When they occur, you want to keep trying for a long time before giving up. However, you don’t want to keep trying constantly. You need to pause between retries:
If I were building a naïve implementation in memory (rather than a more reasonable on disk queue), I could do something like this:
start {
my $retries = 10;
my $success;
for ^10 -> $retry {
$success = sendmail();
last if $success;
sleep 60 * 2 ** $retry;
}
die "Never could send that email." unless $success;
$success;
}
In this code, you have a sendmail
function you assume does all the right things
to send an email. You will try to send 10 times. You check for success and then
you sleep using an exponentially lengthening interval that will spread the
retries out for about the next 18 hours. After that, you will give up. To avoid
blocking the regular work of the process for up to 18 hours, you ran the entire
thing in a start
block. The Promise
returned will be kept whenever the email is sent or broken if the email fails.
There’s a problem, though. This code will block a thread for up to 18 hours. And Threads are a very finite resource: There are up to 64 in the default thread pool. That’s not good. That means our process can still work, but this thread is locked up doing a whole lot of nothing. Threads are expensive resources to use this way. If you have to send email more than once every 15 minutes, you will run out of threads.
How do you solve this? You could reconfigure Raku to use a scheduler with more threads in the resource pool, but the goal of threads is do stuff. Why would you want to waste a thread doing nothing unless you really have no work for them to do?
You can fix it in a way that frees up your threads to keep working and pauses
the task. The await
statement is a way for your code to tell Raku, “You can
have my thread back, if you need it.” So let’s change that code above to read
this way instead:
start {
my $retries = 10;
my $success;
for ^10 -> $retry {
$success = sendmail();
last if $success;
await Promise.in(60 * 2 ** $retry);
}
die "Never could send that email." unless $success;
$success;
}
Now the code will go to sleep for the allotted time, but the thread is freed up for Raku to reuse, which means your application won’t be stuck waiting 18 hours while the mail server is down the next time you need to spam 65 people at once.
This is true in general, not just for pausing for sleep. Any time you use an
await
(so long as you have a version of Raku supporting spec 6.d
or later),
your Raku will able to reuse that thread if the thing being awaited is not ready
yet.
Cheers.