cl-async

Nov 23, 2012

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.