Error handling with futures
So in the last installment of futures updates, we added nice syntax around futures that makes working with them close to native lisp.
The horrible problem is that we forgot to touch on error handling. This is not because I had forgotten about it, but because I had not actually programmed them with error handling in mind. That all changes today!
I made a macro, future-handler-case, that adds near-native (as usual) error handling for future-generating forms. It sits nicely on top of our friends attach, alet, alet*, multiple-future-bind, and wait-for and allows you to easily wrap their future-generating forms with one top-level handler.
(future-handler-case
(multiple-future-bind (name age)
(get-user-from-server)
(format t "got user ~a, who is ~a years old.~%" name age))
(user-not-found ()
(format t "Sorry, user wasn't found.~%"))
(event-error ()
(format t "Error connecting to server.~%")))
So the syntax is like that of handler-case
. The power of it comes from the
fact that even after the stack unwinds, the operations it wraps will still have
the error handlers assigned by future-handler-case
.
The one caveat is that if you jump out of future-handler-case
into a function
that has asynchronous operations, it will not be able to catch errors triggered
asynchronously in that function (because it needs access to either the markup OR
the same stack the markup uses):
(defun process-results (x y)
(alet ((z (get-z-from-server x y)))
(unless (= z 5)
;; this will not be caught, and will be sent to the REPL
(error "Z is incorrect!"))))
(future-handler-bind
(alet* ((x (get-x-from-server))
(y (get-y-from-server)))
(format t "Got x, y: ~a ~a~%" x y)
(process-results x y))
(t (e)
(format t "Got error: ~a~%" e)))
So in the example if x + y != 5, an error will be triggered, but will not be
caught by the error handler because it happens after process-results
has
returned. What you want to take away from this is that if you are calling out to
a function that performs asynchronous operations, it needs to have its own
future-handler-case
wrapping it to be able to safely process any errors that
might occur inside of it.
Aside from that, future-handler-case
closely models handler-case
except that
it exists asynchronously for all future-generating forms it wraps. This makes
error handling when dealing with futures feel more like native lisp.