Home > Raku > Down the error rabbit hole

Down the error rabbit hole

Good error messages are useful because they tell us what we don’t have to check when something went wrong. (see: LTA) I came to that conclusion by trying to replace Bash with Raku. Lets have a look at some innocent piece of code.

my $p = Proc.new;
$p.spawn(<does-not-exist>);
$p.sink;

CATCH {
    default { say .^name, ‚: ‘, .message; .resume }
}

# OUTPUT: «X::Proc::Unsuccessful: The spawned command 'does-not-exist' exited unsuccessfully (exit code: 1, signal: 0)␤»

This error message is wrong. A command that is to be run by exec and derivates can only produce a non-zero exit code if the executable or script is executable, for the OS user accessible, a normal file and not a directory. (There are more but those vary from OS to OS.) As the name of the command suggests it does not exist and therefor can’t produce an exitcode of 1. This bit me shelling out to brtfs to create a snapshot. Because brtfs is not a valid command. It’s a typo. When I finally realised that btrfs works much better 15 minutes had passed. Many unix commands produce different error codes depending on the arguments provided. So this kind of typo can be really confusing when heavy shell scripting is required.

The example above is the only way for Proc::* to produce an exception. That’s troublesome because the language user is not meant to create a Proc object by hand. The subs run, shell and qx{} will never use it in sink context. Instead they will evaluate to False when checked for errors. One can mimic shell scripting by chaining commands with && that way as done in many shell languages. The $shell however will check errno and state ‘command not found’ to aid its users. Rakudo does not do so. We can change that with a few lines of code.

class X::Proc::CommandNotFound is X::Proc::Unsuccessful {
    method message {
        my $symlink = $.proc.command[0].IO.l ?? ' (symlink)' !! '';
        "The command '{$.proc.command[0]}'$symlink was not found."
    }
}

sub use-proc-fatal {
    &run.wrap(-> |c {
        my Proc:D $ret := callsame;
        if $ret.exitcode > 0 {
            with $ret.command[0].IO {
                X::Proc::CommandNotFound.new(:proc($ret)).throw unless .e;
           }
            X::Proc::Unsuccessful.new(:proc($ret)).throw if $ret.exitcode > 0 || $ret.signal > 0;
        }
        $ret
    });
}
use-proc-fatal;
say ‚### run does-not-exist‘;

run 'does-not-exist';
CATCH {
    default { say .^name, ‚: ‘, .message; }
}
run does-not-exist
X::Proc::CommandNotFound: The command 'does-not-exist' was not found.

This is not a proper solution. Firstly Proc should never throw an exception. If at all Raku should fail because that composes better with //. In shell scripting it is quite normal that the execution of a command doesn’t work. Error handling is just part of the game. Since it is so common there should be more and better exceptions. I have a script where I tested a few more cases with a few more exceptions. Having those returned as Failure from run and shell would make qx stop failing silently.

In my backup script I have to wrap each command in a sub and throw exceptions to take advantage of CATCH and LEAVE blocks for error handling and cleanup (there are temporary directories/files and btrfs snapshots). I really wished to have a use Proc::Fatal in the core to help with this task. I believe that needs to be in core because errno is hidden inside Proc inside Proc::Async inside nqp::spawnprocasync inside MVM_proc_spawn_async where I stopped and climbed back up the rabbit hole. In fact I never found the bloody exec*. Also Bash stops being fun after about 50 lines of code. Easy things should be easy.

I’m quite sure I missed a lot of error modes. So this requires futher thought. If you have such please share. If you write a blog post about Raku right now you can expect 500 hits a week. That is 20% up from last year. We are better then the stock market! In a world where money can be printed but jobs can not that’s a real feat.

Categories: Raku
  1. No comments yet.
  1. June 8, 2020 at 14:57

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: