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.
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.