Here is a simple idea for non-local error propagation, inspired by
Erlang's concept of monitors and by the simulation of exceptions in
Go's exception package (not yet in the
golang.org release, but visible
here:
http://code.google.com/p/go/source/browse/src/pkg/exp/exception/exception.go?spec=svnaff697587839774c069bbf35bc120257d4a83865&r=aff697587839774c069bbf35bc120257d4a83865).
The basic idea is that you can associate a channel with a goroutine,
and a message will be sent to the channel when the goroutine
terminates.
--------
Any goroutine can optionally be associated with an exit channel, which
is a normal channel of *ExitInfos. ExitInfo is a struct whose contents
are to be specified, but would include at the least a reason for the
goroutine's exit, and possibly stack information in the case of an
error. To start a goroutine with an exit channel C, write
go f(), C;
We add a function runtime.GoexitWith(interface{}), so the programmer
can specify arbitrary information to be included in the ExitInfo.
If a goroutine does not have an exit channel, then the existing
semantics of the language is unchanged, and runtime.GoexitWith causes
the program to terminate. (Yes, the whole program--think of it as an
unhandled exception.)
If a goroutine does have an exit channel, then built-in errors (e.g.
array out of bounds), runtime.Goexit and runtime.GoexitWith all unwind
the stack, executing defer functions. Then an attempt is made to write
on the exit channel. If the exit channel is no longer referenced by
any other part of the program, or if it is closed, then the program
terminates. Otherwise, an *ExitInfo appropriate to the type of exit
(or nil, if the goroutine terminates normally) is sent on the channel.
If the channel send blocks, the zombie goroutine blocks. If at any
time before the value has been received, the channel becomes
unreferenced or closed, then the program terminates.
--------
Advantages of this idea:
- The existing semantics is unchanged.
- The additional syntax is small, and the additional semantics is
simple and intuitive.
- One mechanism unifies non-local propagation of user errors, handling
of built-in exceptions, and notification of goroutine termination in
the presence of runtime.Goexit (and, when distributed, in the presence
of network or remote node failure).
- Improves the simulation of exceptions in package exceptions
mentioned above: now the handler can be a global function instead of
having to be passed along, and certain errors, like holding the
handler after it has outlived its Try or having a sub-goroutine call
it, are no longer possible.
- There is a wealth of experience with this mechanism in the Erlang
community. In fact, Erlang folks will tell you that the real power of
Erlang lies in the supervisor patterns they have built that rely on
this ability to cleanly and reliably detect process (= goroutine)
termination.
The main disadvantage is that, despite my efforts to legislate
otherwise, it is easy to ignore errors. You can't create an exit
channel and throw it on the floor -- my spec handles that case -- but
you can create an exit channel and never receive from it, and you will
accumulate plenty of zombie goroutines and as many unhandled errors.
And unfortunately, it is harder to see that problem by inspection of
the source than it is to see try { ... } catch () {}.