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 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 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.
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
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).