Asynchronous Inter-Process Communication
Lots of big words in the title. In simpler terms, it means running a program in
the background and interacting with it as input and output becomes available.
The tool in Raku for doing this work is called
Proc::Async
. If you’ve ever dealt
with the pain of trying to safely communicate with an external process, writing
to its input and reading from its output and error streams and hated it, I think
you’ll like what Raku has built-in.
First, let’s contrive a problem. Let’s make an external program that takes strings as input and reverses them and outputs them on the standard output. Meanwhile, it reports on standard error whether or not the given string is a palindrome or not. We could write this program like so:
for $*IN.lines -> $line {
my $rline = $line.flip;
say $rline;
note ($rline eq $line);
}
And just to make it clear, for this sample input:
spam
slap
tacocat
We will get this output:
maps
False
pals
False
tacocat
True
The True
and False
lines being standard error and the other lines being on
standard output. Clear? Good.
Next, to finish our contrived problem, we need to interact with this program and
just write out a message like tacocat is a palindrome!
because it is exciting
whenever we see a palindrome. We want to output nothing otherwise. Let’s use
Proc::Async
to interact with our other program, we’ve called palindromer
for
giggles.
react {
my $palindromes = Supplier.new;
my $p = Proc::Async.new: './palindromer', :w;
my @rlines;
my @palindrome-checks;
# Echo our own input
whenever $*IN.Supply.lines -> $line {
$p.say: $line;
# Let palindromer know when we run out of input
LAST { $p.close-stdin }
}
# Watch for the reverse lines on palindromer's standard output
whenever $p.stdout.lines -> $rline {
if @palindrome-checks.shift -> $is-palindrome {
$palindromes.emit: $rline;
}
else {
push @rlines, $rline
}
}
# Watch for the True/False output from palindromer's standard error
whenever $p.stderr.lines -> $is-palindrome {
if @rlines.shift -> $rline {
$palindromes.emit: $rline if $is-palindrome eq 'True';
}
else {
push @palindrome-checks, $is-palindrome;
}
}
# PALINDROMES ARE EXCITING!
whenever $palindromes.Supply -> $palindrome {
say "$palindrome is a palindrome!";
}
# Quit when palindromer quits
whenever $p.start { done }
}
Now, if we pipe the same input as before into our new program, we should get this output:
tacocat is a palindrome!
Our code deals with the potential problem of standard output and standard error
getting out of sync by using a queue to accumulate extra values from each side.
We feed the discovered palindromes into a central
Supplier
named $palindromes
so we can
have a single place for printing the palindromes we find.
A couple key points to be aware of when using Proc::Async
:
- Always put the
.start
call after your tap on standard output and standard error. Otherwise, there could be problems with missed lines (i.e., lines emitted before you are listening). Raku will warn you if it detects that you have done this, by the way. This method returns aPromise
that is kept when the program is finished. - Make sure you use
.close-stdin
when you are finished with input to make sure the other program knows that you’re done.
Otherwise, there are a few more interesting features, such as
.ready
amongst others,
which you might want to read more
about in the Raku reference
documentation online.
Cheers.