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;
}