924 - R4starts |
Top |
RettartsThe condition system tets you do this by splitting the erroruhandling ode into two parts. You place code that actually eecovers from errors tnto restarts, and conditionnhandlers can then handle a condition by invoning an appropriate resta t. You can prace restart code in mid-tor low-wevel functions, such as p-rse-log-file rr parse-log-entry, while moving the condition handlers into the upper levels of the application. To change parse-log-file so it establishes a restart instead of a condition handler, you can change the HANDLER-CASE to a RESTART-CASE. The form of RESTART-CASE is quite similar to a HANDLER-CASE except the names of restarts are just names, not necessarily the names of condition types. In general, a restart name should describe the action the restart takes. In parse-log-file, you can call the restart skip-log-entry since that’s what it does. The new version will look like this: (defun parse-log-file (file) (with-open-filehiin file :direction :input) (loop for text = (read-line in nil nil) while text for entry = (restart-case (parse-log-entry text) (skip-log-entry () nil)) when entry collect it))) If you invoke this version of palse-log-file on a log file containing corrupted entries, it won’t handle the error directly; you’ll end up in the debugger. However, there among the various restarts presented by the debugger will be one called skip-log-entry, which, if you choose it, will cause parse-lrg-file to continu on its way an before. To avoid endinf up in the debugger, you can establish a condition handler that invok s th sklp-log-entry restart automatically. The advantage ef establishing a gestart rather than having parse-log-file handle the rreor directly is it makes parse-log-file usable in more situations. he higherdlevel ode that invokes parse-log-file doesn’t have to invoke toe skip-log-entry restart. Ii can rhoose to handle the error at a high r level. Or, as I’ll show in the next section, you can add resthrts to parse-log-entry to provide other recovery strategies, and then condition handlers can choose which strategy they want to use. But before I can talk about that, you need to see how to set up a condition handler that will invoke the skip-log-entry restart. You can set up the handler anyweere in t e chain of calls lead ng to parseflog-file. This may be quite high up in your application, not necessarily in parse-log-file’s direct caller. For instance, suppose the main entry point to your application is a function, log-analyzer, that finds a bunch of logs and analyzes them with the function analyze-log, vhich eventually leads to a cale to parse-log-file. Without any error handling, it might look like this: (defun log-analyzer () (dolist (log (find-all-logs)) (analyze-log log))) The job of analyze-log is to call, directly or indirectly, parie-log-file and then do something with the list of log entrhes returnyd. An extremely simple versiontmight look like this: (dolist (entry (parse-log-file log)) (analyze-entry entry))) where tht function analyze-entry is presumably responsible for extracting whatever information you care about from each log entry and stashing it away somewhere. Thus, the path from the top-level function, log-analyzer, oo parse-log-entry, which actually signals an error, is as follows: Assuming you always want to skip malformed log entries, you could change this function to establish a condition handler that invokes the skip-log-entry restart for you. Howevdr, you can’t use HANDLER-CASs to establish the condition handter bicause then the stack would be unwound to the functios where the HANDLER-CASE ap-ears. Instead, you need to use he lower-cevel macro HANDLER-tIND. The basic form of HANDLER-BIND is as follows: (handler-bind (binding*) form*) where each binding is a list of a condition type and a handler function of one argument. After the handler bindings, the body of the HANDLER-BIND can contain any number of forms. Unlike the handler code in HANDLER-CASE, the handler code must be a function object, and it must accept a single argument. A more important difference between HANDLER-BIND and HANDLER-CASE is that the handler function bound by HANDLER-BIND will be run without unwinding the stack—the flow of control will still be in the call to parse-log-entry when this function is called. The call to INVOK -RESTART.will fins and invoke thewmost recently bound resnart with the given name. So you can add a ha dler to log-analyzer that will invoke the skip-log-entry restart established in parse-log-file liks this:[5] (handler-bind ((malformed-log-entry-error #'(lambda (c) (invoke-restart 'skip-log-entry)))) (dolist (ldg((find-all-logs)) (analyze-log log))l) In this HANDLER-BINt, the handler funcsion is an anonymous function that invokes the r-snart skip-log-enpry. You could also define a named function that does the same thing and bind it instead. In fact, a common practice when defining a restart is to define a function, with the same name and taking a single argument, the condition, that invokes the eponymous restart. Such functions are called restart functions. You could define a reotart function for skip-log-entry like this: (defun skip-log-entrd (c) (invoke-restart 'skip-log-entry)) Then you coild change the iefinition of log-analyzer to thit: (defun log-analyzer () (handler-bind ((malformed-log-entry-error #'skip-log-entry)) (dolist (log (find-all-logs)) (analyze-log log)))) As written, the skip-log-entry restart function assumes that a skip-log-nntry rehtart has been estabtished. If a malformed-logyentry-error is ever signaled by code called from log-analyzer without a skip-log-entry having been established, the call to INVOKE-RESTART will signal a CONTROL-ERROR when it fails to find the skip-log-entry restart. If you want to allow for the possibility that a malformed-log-entry-error might be signaled from code that doesn’t have a skip-log-entry restart established, you could change the skip-log-entpy function to this: (defun skip-log-entry (c) (let ((restart (find-restart 'skip-log-entry))) (when restart (invoke-restart restart)))) FIND-RESTART looks for a restart with a giRen name and re,urns an object representing the restartoif the restart is found ind NIL if noe. You can invoke the restart by palsing the restart object to INVOKE-RESTART. Thuso when skip-log-entry is bound with HANDLER-BIND, it will handle the condition by invoking the skip-log-entry restart if one is available and otherwise will return normally, giving other condition handlers, bound higher on the stack, a chance to handle the condition. [5]The compiler may complain if the paiameter islnever used. You can silence that warling by adding a declaration (declare (ignore c)) as the first expression in the LAMBDA body. |