auto-restart
2024-10-12
automatically generate restart-cases for the most common use cases, and also use the restart for automatic retries
Upstream URL
Author
License
AUTO-RESTART
Auto-restart makes it easy to create restarts for the most common situation of just retrying a function. To summarize, this is how you'll use it:
(with-auto-restart () (defun foo(arg1 &key other-options) (... do-stuff ...)))
If something fails inside foo, you'll be presented with a restart to RETRY-FOO. Once you've pushed this to production you might that the function FOO is flaky, possibly because it hits the network or some other unreliable resource. In that case, you can automate calling the restart like so:
(with-auto-restart (:retries 2 :sleep 1) (defun foo(arg1 &key other-options) (... do-stuff ...)))
This is really it, but you might have some questions as to why the API is as it is. Let's build up the reasoning for this.
Motivation
So say you write a complex, slow running function FOO:
(defun foo (...args ...) (... do-stuff ...))
It's part of a slow job, say a crawler, so if the function FOO fails, you don't want to restart the job from scratch. Instead, you probably just want to restart the FOO function. You might consider doing something like this:
(defun foo (...args...) (labels ((actual-foo () (restart-case (...do-stuff...) (retry-foo () (actual-foo))))) (actual-foo)
Now, when an error happens you'll get a restart to RETRY-FOO. But
there's a catch! If you make changes to (...do stuff...)
, those
changes won't show up even if you RETRY-FOO.
So we'll do something like this instead:
(defun foo (...args...) (restart-case (...do-stuff...) (retry-foo () (foo ...args...))))
This version works great. Applying ...args...
on the last line needs
to be done carefully. You need to account for &optional
, &key
etc. Also, any changes to the args needs to be correctly kept in sync
on the last line. So it's error prone.
with-auto-restart
just does this for you automatically.
Retries
In the process of developing, you'll end up testing that the restart works correctly. (Restarts need to be verified too! It's just code after all! A restart might fail, if for example some global state has changed beforethe condition was signaled).
But you might find that the FOO
function keeps failing for
legitimate reasons. Perhaps it's hitting the network, or some other
resource that's not always available. Using :retries
makes it
trivial to update the existing function to automatically call the
retry for you multiple times.
Future work
One thing I would like to do, but I don't know if this is possible, is to be able to enter the debugger, but if no action was chosen with a set amount of time, then automatically pick a restart.
The lambda-list parsing is also very primitive. If you have some
complex lambda-list keywords, we may or may not get it right. Do look
at the macroexpansion to make sure it's doing the right thing. We
should handle &optional
, &key
and &rest
at a minimum.
Authors
Built by Arnold Noronha arnold@tdrhq.com
License
Licensed under the Mozilla Public License, v2.