“Try again” with Exceptions

By: on January 4, 2011

Like many modern languages, Smalltalk has the concept of an exception. When an exception’s signalled, the current stack of contexts – activation frames – is gradually unwound (with unwind blocks – what in Delphi would be called try-finally blocks – being executed at each stage), until a context handles that particular exception.

If no context in the stack handles the Exception, the Exception’s #defaultAction gets called. (How? The first context in the stack tries to find its nextHandlerContext, only it doesn’t have one – it’s nil. UndefinedObject (nil’s class) defines #handleSignal:, which invokes the Exception’s #defaultAction.)

Since Smalltalk has the call stack available for inspection, introspection and general mucking-about, Smalltalk also supports a Exception that can retry the action that signalled the Exception.

Brief interlude: Smalltalk’s stack (or Squeak’s, at least) consists of a linked list of ContextParts. If you think of the stack growing upwards, a ContextPart’s sender is the next ContextStack down.

When you retry an Exception, it unwinds the stack to the offending context’s sender, and resumes execution.

NameLookupFailure is one such Exception.

For instance:

  Utilities
    informUser: 'Ensuring your network connection works...'
    during: [
      NetNameResolver
        addressForName: 'squeak.org'
        timeout: 30].

(This comes from an old version of RemoteHandMorph>>ensureNetworkConnected.)

That’s a loop. No, really. Let’s say you don’t have a network connection. NetNameResolver tries to resolve ‘squeak.org’ and naturally fails. That signals a NameLookupFailure. Since there are no handlers for that exception, the exception’s sent the #defaultAction message. That pops up a dialog asking you to ‘Retry’ or ‘Give Up’. If you hit ‘Retry’, the stack unwinds to the sender of the name lookup, namely, Utilities class>>informUser:during:. Execution proceeds, evaluating once more the block [ NetNameResolver … ]. You can Retry as often as you like. Plug in your network cable, and when the exception retries, hey presto, Utilities class>>informUser:during: finishes executing.

There’s a bug in the above code, by the way. What if you want to give up? Well, then RemoteHandMorph>>ensureNetworkConnection returns, and the code that called it naturally assumes that you have a network connection. Oops. The solution’s simple, though: have RemoteHandMorph>>ensureNetworkConnection return a Boolean; false means “Hey, we tried, but there’s no network connection. Sorry!”. The current implementation does so:

  | address |
  Utilities
    informUser: 'Ensuring your network connection works...'
    during: [
      address := (NetNameResolver
        addressForName: 'squeak.org'
        timeout: 30)].
  ^ address notNil.

You still have that tricky loop-using-exception (known to cause heebie-jeebies in at least one of my colleagues), but you can safely bail out of operations that require a network connection.

How to prevent the heebie-jeebies? Socket class>>ping: shows us.

  [sock waitForConnectionFor: 10]
    on: ConnectionTimedOut
    do: [:ex |
      (self confirm: 'Continue to wait for connection to ', hostName, '?')
        ifTrue: [ex retry]
        ifFalse: [
          sock destroy.
          ^ self]].

You’re still looping using an exception, but it’s explicit.

Share

2 Comments

  1. Duncan says:

    What’s bad about the loop-using-exception technique? Is it similar to looping with tail-call recursion, but without tail-call optimization?

  2. Frank Shearar says:

    Duncan, my difficulty with the technique is that it’s surprising. Your loop spans an arbitrary number of methods, with no apparent connection: your original block, the method that raises the exception and, in this case at least, the default handler. Reading the source of these three places doesn’t hint at the runtime behaviour.

    Even though the original snippet and the socket connecting loop are both using loop-using-exception, the latter case is explicit about how it works. You don’t have to sit tracing the execution of the loop in a debugger – including the unwinding of the stack as the exception propogates – to see what’s happening.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*