User input with timeout doesn't work as hoped
It's because you're talking to the wrong call to prompt
, which is in a promise which has closed on a different variable $str
. The second and later calls to prompt
block, waiting for the first call to finish. But the $str
that is to receive the value of the first call is out of scope, so nothing happens.
That sounds very strange, but here's an experiment you can run to help your intuition along while I dissect it more fully: run the script, wait for the timeout, then enter q
twice in rapid succession. The script quits after the second one. Why?
On the first loop, we declare a variable $str
which I'm going to call "$str
number 1" and create a Promise
that closes over $str
number 1 and calls prompt
. prompt
attaches to STDIN
and doesn't return until it sees a newline. When the timeout expires, that call to prompt
is not interrupted. It's still running. Still waiting. The promise it's attached to (let's call it $user
promise 1) is still active even though the variable $user
is about to go out of scope.
On the second loop, we declare a new variable $str
("$str
number 2"), create a Promise
that closes over it, and call prompt
again. But another call to prompt
is still using STDIN
, so the new call blocks and waits for STDIN
to become available. If you type something now, it will be seen by the original call to prompt
, which was attached to $user
promise 1 and closed over $str
number 1.
$str
number 1 is updated when prompt
returns, but it doesn't matter because you stopped looking at it. The if $str eq 'q'
conditional is going to inspect $str
number 2, because that's the variable that was declared in the current loop.
The second call to prompt
then immediately asks for input, and if you type q
before the timeout expires, it updates the version of $str
that it closed over, $str
number 2. Since that's the one your conditional is inspecting, the loop terminates.
Every timeout starts a new prompt
without terminating the old one, which means that the input the user types is never attached to the same $str
that you are inspecting. Even if you inspect the original variable, the subsequent calls to prompt
still happen and will keep prompting even after execution has left the block.
Since prompt
doesn't have a way to specify a timeout and Raku doesn't have a way to "kill" scheduled Promises
, I don't think you get to solve this problem with prompt
.
The logical issue in this code, is that the timeout Promise will trigger after 5 seconds, even if someone has entered something in the previous iteration. And so it will set $str
at seemingly random times.
There is a simple solution: just make sure that you do not assign $str
in the timeout code if it has already been set:
$str //= 'Timeout';
For this example, it doesn't really matter, but generally you don't want code to be executing willy nilly, so it would be better to actually de-activate the Promise
. Unfortunately, you cannot do that with the Promise
interface. But the Promise.in
method is actually a wrapper around the ThreadPoolScheduler.cue
method, which does return a Cancellation
object (https://docs.raku.org/routine/cue).