2015 twenty-four merry days of Perl Feed

No More Leaking Glue

Process::Status - 2015-12-17

Perl is often used as a "glue language". You have a bunch of other programs that you want to automate from one place, so you use Perl. You put the programs carefully in position, grab the bottle of Krazy Glue, drizzle it everywhere, and bask in the glow of what you've accomplished.

Only then, inevitably, you can't put down the glue bottle. It's not that you've gone mad with a need to automate all the things - although that's a possibility, too. This time, though, you've glued the glue bottle to your hand, and no matter what those people on the GlueGaffes subreddit tell you, the only way to get that bottle off your hands is to become some sort of snake monster and shed your skin. This, presumably, is how Python programmers come to be.

Well, the abstraction leak in our Perl glue layer might not be so bad, but it's still a big annoyance. It's all those places where Perl reminds you, "Hey! Hey! Lots of me is just a little wrapper around C library stuff!" Isn't that great? This is why you end up with stat returning a thirteen element list, for example. (Wait, the Perl Advent Calendar has never featured an article on File::stat? Well, check it out later, because that's not the problem we're talking about right now.)

Right now the problem we're talking about is running subprocesses. There are a number of ways to do this; You might use system. You might use open. You might use fork. They all have one thing in common: when the process you started via those means exits, you get its exit status in the same place: $?

$? (known by devotees of the Way of the Dog as $CHILD_STATUS) is a global variable that gets populated with the numeric status of the ex-process. It's supposed to be easy to remember $? because it's the same name as in shell programming...but in the shell, $? is the exit value of the program. If a program exits 75, then $? is 75. In Perl, that would be 19200...and now you just glued your finger to the keyboard.

Perl is leaking the way that status code is packed into the status integer in C: eight bits of exit value, seven bits of signal identifier, and one bit to flag whether the process dumped core. Instead of giving you three variables, you get one number between 0 and 65535. Great!

In C, there are macros for getting the data you wanted.

exit_status = WEXITSTATUS(status);
signal = WTERMSIG(status);
exit_status = WCOREDUMP(status);

In Perl, you typically find people using bit-shift operators to extract the part of the return code they care about:

return unless system($some_command);
die sprintf "program exited %i", $? << 8;

...which the hardened Perl programmer tends to recognize at a glance (and doesn't even notice how ridiculous it is to type this all the time).

Speaking of things even we Perl veterans fail to notice: When the code accidentally uses << instead of >>. Ooops. Did you notice I should have actually written the above code as:

return unless system($some_command);
die sprintf "program exited %i", $? >> 8;

But that's the problem when you're using non-obvious syntax. It's non-obvious! And even when someone does a proper code review they don't catch that kind of thing.

We also also probably don't catch the situations where we might care about the signal but then we totally forget to check or print it in our code. Why did we fail to do that? Maybe because it was more work, and maybe because we have a quota of one bitwise operation allowed per Perl program per day and we hit it with the first one.

Of course, we can eliminate these stupid errors with everyone's favorite core module... POSIX!

# ARGH MY EYES
use POSIX qw(WEXITSTATUS);

return unless system($some_command);
die sprintf "program exited %i", WEXITSTATUS($?);

Ha ha just kidding. I mean, you could do that, but then you'd be using POSIX.pm, and you'd have to hold down the shift keys a whole lot when typing, and you'd have to remember to add that code for signals! Also, seriously, POSIX.pm.

This is why Process::Status exists. It's such a useful module that Ruby stole it from us, then travelled back in time 15 years to make it look like they wrote it first.

use Process::Status;

system($some_command);
Process::Status->assert_ok;

If your program exited 0, nothing happens. If it exited non-zero, you get an exception like:

  program exited 13, caught SIGWOOP; dumped core at line...

There are a bunch of other useful methods for finer-grained handling, but for the most part the only three you need to know are self-explanatory:

  • as_string

  • assert_ok

  • is_success

If you ever run subprocesses from Perl, use Process::Status.

If you don't ever run subprocesses from Perl, I don't even understand your life.

SEE ALSO

Gravatar Image This article contributed by: Ricardo SIGNES <rjbs@cpan.org>