All Manuals > Delivery User Guide > 3 Writing Code Suitable for Delivery

3.2 Error handling in delivered applications

Normally you do not expect an application user to debug it, so you never want your delivered application to call the debugger. Obviously you try to achieve that by making the application error-free, but it is difficult to guarantee that the application never calls error. You therefore handle errors in the application, such that even if an error occurs it does not enter the debugger.

There are two classes of error an application is likely to need to handle: errors generated by the application, and errors generated by the Lisp system.

3.2.1 Handling errors generated by the application

Error conditions that can occur in your application domain can be handled easily enough if you define your own error handling or validation functions to trap them. For instance, you might have the following code to detect an error condition and call error:

.....
(let ((res (call-something)))
  (when res
    (generate-error res))
.....
(defun generate-error(res)
  (error 'application-error     
         :error-number res))

You can easily define a version of generate-error that does all the work without calling error:

(defun generate-error (res)
  (let ((action
         (capi:prompt-with-list
          '(("Abort Operation" . abort)
            ("Retry Operation" . retry)
            ("Ignore Error")
            ("Quit" . stop-application)
            ("Do Something Else" . do-something-else))
          (find-error-string res)
          :print-function 'first
          :value-function 'rest)))
    (case action
      ((abort retry) (invoke-restart action))
      ((nil))
      (t (funcall action)))))

3.2.2 Handling errors generated by the Lisp system

Errors generated by the Lisp system, rather than the application domain, are a little harder to deal with.

Suppose your application performs an operation upon a file. The application calls a system function to complete this operation, so when there is no error system, any errors it generates must be caught by an error handler in the application itself.

Error handling can be dynamically-scoped or global.

Dynamically-scoped error handling is done by wrapping cl:handler-bind or cl:handler-case around a body of code. This has the advantage that it allows you to tailor the response to errors in specific pieces of code and for specific types of error. It has the disadvantage that it is not global. If you put it in the process function (the function argument to mp:process-run-function) it will apply only to the code that is executed in that process, but you still need it in each process.

The global error handling is done by setting cl:*debugger-hook*. This applies to anything that tries to enter the debugger, in particular any cl:error call that was not handled otherwise. It has the advantage that it really is global, but the disadvantage that it cannot be tailored locally.

Since the cl:*debugger-hook* is applied only if the error was not handled, the two mechanisms can be used at the same time and typically they are. The dynamically-scoped ones are used to give the accurate response, while the global one used to catch any error that is not handled for some reason.

In either case, the handling means that some of your code is being executed. Either it is the function is bound to the error type in cl:handler-bind or set to cl:*debugger-hook*, or the body in the clause in cl:handler-case. This code should the "right thing" to deal with the situation. For unexpected errors, that normally would mean generating some log of the problem, telling the end-user that something went wrong, maybe giving the user some options of actions, and aborting (note that cl:handler-case already aborted when the code is executed). Note that the type of condition passed to handlers may be affected by the delivery level (see 10.5 The condition system in delivered applications).

The log of the problem would normally be a bug form, which you can generate by:

(dbg:output-backtrace :bug-form ...)

If you can obtain the bug form, it will give you (the programmer) a chance to identify the reason for the error. There is also dbg:log-bug-form which writes it to a file. You would not normally show the bug form to the end-user. Instead, in a GUI application you will probably want to display a dialog informing the user that something went wrong and maybe giving them some options. In a console application you probably want to just print a short message.

There is a simple example of using cl:*debugger-hook* in:

(example-edit-file "delivery/debugger-hook/application-with-errors")

Delivery User Guide - 01 Dec 2021 19:35:03