Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When would I want to resume a Perl 6 exception?

Perhaps my real question is "Is this a feature appropriate for Learning Perl 6"? Based on Should this Perl 6 CATCH block be able to change variables in the lexical scope?, it seems the simplest example might be beyond a simple example.

In that question I was working with something that appears to be silly or better down in another way for that particular problem because I was playing with the feature rather than solving a problem.

There's the documented use of warnings as special sort of exceptions ("control exceptions") where you get the message, can catch it if you like, but can also ignore it and it will resume on its own (although I was rather stupid about this in Where should I catch a Perl 6 warning control exception?).

Beyond that, I'm thinking about things where the caller can handle a failure outside of the scope of the callee. For instance, reconnecting to a database, fixing missing directories, and other external resource issues that the callee doesn't have responsibility for.

In reading about this sort of thing in other languages, the advice has mostly been to not use them because in "real world" programming people tend to not actually handle the problem anyway.

The answer to C# exception handler resume next seems to say it's poor practice and ugly code. I certainly haven't figured out a way to hide a bunch of code in the callee.

I hacked up this example, although I'm not convinced it's a good way to do it or something to recommend to beginners. The program looks for a PID file when it starts. If it finds one, it throws an exception. Handling that exception checks that the other instance is still running, which might throw a different type of exception. And, there's the one to handle the file IO problems. The trick is that the X::MyProgram::FoundSemaphore can resume if the other program isn't running (but left its PID file behind).

class X::MyProgram::FoundSemaphore is Exception {
    has $.filename;
    has $.this-pid = $*PID;
    has $.that-pid = $!filename.lines(1);

    method gist {
        "Found an existing semaphore file (pid {.that-pid})"
        }
    }

class X::MyProgram::StillRunning is Exception {
    has $.that-pid;
    has $.os-error;

    method gist {
        "This program is already running (pid {self.that-pid})"
        }
    }

class X::MyProgram::IO::OpenFile is Exception {
    has $.filename;

    method gist {
        "This program is already running (pid {self.that-pid})"
        }
    }

sub create-semaphore {
    state $filename = "$*PROGRAM.pid";
    END { unlink $filename }

    die X::MyProgram::FoundSemaphore.new(
        :filename($filename)
        ) if $filename.IO.e;

    my $fh = try open $filename, :w;

    # open throws Ad::Hoc, which could be more helpful
    die X::MyProgram::IO::OpenFile.new(
        :filename($filename),
        :os-error($!),  # role X::IO-ish
        ) unless $fh;

    $fh.print: $*PID;
    }

BEGIN {
    try {
        CATCH {
            when X::MyProgram::FoundSemaphore {
                my $proc = run qqw/kill -0 {.that-pid}/;
                X::MyProgram::StillRunning.new(
                    :that-pid(.that-pid) ).throw
                    if $proc.so; # exit code is 0, so, True
                unlink .filename;
                .resume;
                }
            default { say "Caught {.^name}"; exit }
            }

        create-semaphore();
        }
    }

sub MAIN ( Int $delay = 10 ) {
    put "$*PID sleeping for $delay seconds";
    sleep $delay;
    }
like image 310
brian d foy Avatar asked Apr 09 '17 09:04

brian d foy


1 Answers

Resumable exceptions certainly aren't something I've found myself reaching for in Perl 6. I don't think I've used them in "userspace" code at all yet. A resumable exception turned out to be the right way to implement the emit function, used in supply and react blocks. The take function used in gather is also implemented using a resumable exception, and - as you've already discovered - warn uses them.

I suspect the last of these - warn - is the only case that the typical Perl 6 user will be interested in. Capturing warnings and sending them elsewhere - perhaps to a log file or log server - is a fairly reasonable thing to need to do. So far as Learning Perl 6 goes, that is probably the obvious useful example of a resumable exception.

I think it's significant that all of the use-cases that take advantage of resumable exceptions in Perl 6 itself turn out to be things classified as "control exceptions". Control exception are essentially normal exceptions at an implementation level: they involve a non-local transfer of control. They are made distinct at the language level because it would be rather awkward to use Perl 6 if your emit, take, warn, next, last and so forth stopped working because of a CATCH block with a default swallowing the control exceptions!

However, it's also a bit of "do as I say, not as I do": while Perl 6 is happy to use the exception system to implement non-local flow control, it somewhat fences it off in a dusty corner of the language rather than holding it up as an example of something to do. And for good reason: usually, code that uses exceptions to do flow control is hard to follow, and that goes double for resumable exceptions. The other big risk is that such exceptions can be swallowed up by code that uses a bare try or a CATCH with a default - making it a rather fragile thing to do in a larger codebase.

I'd imagine the best uses of resumable exceptions will turn out to be as an implementation strategy for things that users won't think about in terms of exceptions at all - just as is the case with take and emit (and, most of the time, warn). And, as with the existing examples of resumable exceptions, the thing being resumed will be an exception type that was specifically designed to be thrown in resumable situations and only used in cases where that is a sensible thing to do. Until Perl 6 provides a way to define custom control exceptions, however, I'd be rather reluctant to do this; the try/default swallowing issue makes it just too fragile.

like image 113
Jonathan Worthington Avatar answered Oct 20 '22 12:10

Jonathan Worthington