Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)

H. S. Teoh hsteoh at quickfur.ath.cx
Wed Feb 22 21:12:35 PST 2012


On Thu, Feb 23, 2012 at 04:37:37AM +0100, Kapps wrote:
[...]
> The Condition approach you propose is fairly similar to what I was
> thinking of, as an approach to fixing Problems that occur (not
> necessarily exceptions). One of the fundamental problems I have with
> exceptions is that, despite intended to be recoverable as opposed to
> Errors, they are not recoverable. The operation still fails when an
> exception is thrown, and you have to restart all over again (even if
> it's something easily recoverable from).

Exactly!! As soon as the stack unwinds, you've just left behind the very
context that would have been the best position to fix problems from. In
a properly-written program, the caller of the function treats the
throwing function as a black box, so once the stack unwinds back to the
caller, there's no more possibility of repairing the problem. The best
you can do is to repeat the operation from scratch and hope it works
this time.

That's what I like so much about the Lisp approach. The high-level code
registers handlers that are qualified to make high-level decisions, and
the low-level code implements the low-level details of how to go about
fixing the problem.


> The other issue, is that exceptions are just poorly designed for
> multithreaded and event based applications. If you queue some work to
> be done, you likely won't even know if an exception is thrown. If you
> have a callback / event based approach, you have to do the equivalent
> of returning error codes (something exceptions were designed to
> avoid), except with passing in error codes instead. An example is .NET
> forcing all asynchronous operations to have a Begin____ and an End____
> with the End____ rethrowing whatever problems occurred during the
> operation (even if they were easily solveable if the caller simply had
> immediate access to handle them).

I've thought about this too, though I don't currently know what's the
right way to implement it. For an event-based program, Conditions aren't
good enough, because high-level handlers are deregistered once the
registering function's scope exits. This is to prevent unrelated
functions from polluting each other's handler stack, with handlers from
one function catching stuff it shouldn't from other code.

But for an event-based program, you *need* persistent problem handlers.
The question is how to prevent persistent handlers from getting problem
notifications that they weren't designed to handle.

One idea, which I haven't really thought through yet, is to introduce
the concept of Domains. A Domain represents a particular operation
carried out by some sequence of events and their respective callbacks.
For instance, in a network app, a "Login" operation involves calling a
Login function in the communications layer, with a callback to notify
when the login process is completed. The Login function in turn calls
the Connection layer to connect to the remote server, with a callback to
inform the Login function when the server is connected. This callback in
turn calls the SendMsg function to initiate the authentication process,
with its respective callback, etc.. The sum total of all these
components: the Login function, its callback, the Connection layer, its
callback, etc.., comprise the Login domain.

Domains may be nested; for example, the Connection layer may call the
Socket layer and the Packet layer, all of which have their own set of
callbacks. This "initiate connection" process constitutes a subdomain of
the Login domain, and the "send packet" process constitutes a subdomain
of the "initiate connection" domain.

Each problem handler is then associated with a particular domain (and
its subdomains). Whenever a problem occurs, the problem handling system
identifies in which domain the problem happened, and searches up the
domain hierarchy (starting from the lowest-level domain, like "send
packet", up to "initiate connection" up to "Login") for problem
handlers. The problem handler then can take the necessary actions to
correct the problem.

The Condition system can (and probably should) be adapted to this model
too. For example if the "initiate connection" domain gets a timeout
error from the packet layer, the best place to attempt retry is right
there in the Connection layer. However, that's not the best place to
decide whether or not to retry; it may be the case that a higher-level
handler (i.e. handler higher up the domain hierarchy) is in a better
position to make this decision. So somehow they need to be able to
cooperate with each other.

The details of how this can work still need to be worked out.


> [...]
> (Unfortunately, unless you have a global problem handler for the
> entire transfer operation, this still suffers from the second issue
> about how those exceptions do not get carried up to the caller that
> starte the operation.)

I think with the concept of Domains, we can solve this. Problems that
are found in lower level domains bubble up the domain hierarchy until
they find a handler that can deal with them. Eventually if they are
unhandled, they get to the top level, which is the caller that started
the operation.

But I haven't worked out how to actually implement such a system.


> The transfer approach might not be the greatest example, but it
> demonstrates something that (IMO) Exceptions are misused for, which
> affects the design of Exceptions themselves as it attempts to hammer a
> solution they're not great for in with them.

The transfer example is a pretty good use case for how to propagate
errors in an event-based system. It's definitely worth investigating,
although it's probably beyond the scope of the Conditions system.


T

-- 
It's amazing how careful choice of punctuation can leave you hanging:


More information about the Digitalmars-d mailing list