Sterling has too many projects Blogging about Raku programming, microcontrollers & electronics, 3D printing, and whatever else...

Asynchronous Inter-Process Communication

| 562 words | 3 minutes | raku advent-2019
Vintage telephones on the wall

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:

  1. 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 a Promise that is kept when the program is finished.
  2. 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.

The content of this site is licensed under Attribution 4.0 International (CC BY 4.0).