Peter Seibel <peter@javamonkey.com> wrote:
+---------------
| Given the flexibility of the condition system for managing
| coordination of code at different levels of the call stack, I'm having
| a hard time thinking of what I'd use CATCH/THROW for.
|
| Does anyone have any favorite idioms they still use that feature
| CATCH/THROW?
+---------------
I ran into a situation just last week where CATCH/THROW was what came
first to mind as a solution, though thinking back on it I suppose I
could have used conditions just as easily...
Under Unix a given process can be in only one state at a time w.r.t. the
handling of a given Unix signal, but in implementations such as CMUCL
(on x86) which provide "green threads"[1], you often want different
threads to handle signals differently, at least in their control-flow
responses. In particular, I wanted a SIGPIPE (which is what you get if
your web server code is writing to a socket and a user hits the <STOP>
button on the client browser) to be handled by the thread that was writing
to the closed socket, *not* the MP::*INITIAL-PROCESS*. My perhaps-ugly
solution was as follows:
In the global initialization code:
(defun sigpipe-handler (signal code scp)
(declare (ignore signal code scp))
(throw 'sigpipe 'sigpipe))
;; Remember initial state & enable local handler.
(defvar *initial-sigpipe-handler*
(system:enable-interrupt :sigpipe #'sigpipe-handler))
In the per-thread server code [where the worker routine SERVE-REQUEST-1
will in the normal case do a (progn (finish-output stream) (close stream))
before returning]:
(defun serve-request (func request)
(let ((stream (http-request-stream request)))
(when (eq 'sigpipe (catch 'sigpipe
(serve-request-1 func request stream)))
(log-msg "serve-request[~d]: SIGPIPE on fd ~d: ~a"
*request-sequence-number*
(unix::fd-stream-fd stream)
(http-request-self request)) ; sanitized URI
;; Avoid fd leakage.
(ignore-errors (close stream :abort t))))) ; [2]
Note: SIGPIPE always occurs during a "write()" system call [or "close()",
which is why the above code keeps the CATCH active around the (close stream)
in SERVE-REQUEST-1] and thus is thread-synchronous, and thus the THROW
in the handler will be in the same thread context as the CATCH wrapped
around the offending "write()". [Caution: The same will *not* necessarily
be true for other Unix signals.]
If someone has something significantly cleaner to suggest, feel free
to do so. As noted at the beginning, I suppose I could have defined a
specific condition and used HANDLER-CASE/ERROR to get the same effect.
But in such a confined context CATCH/THROW seemed more "lightweight"
somehow. [Hmmm... Maybe my former C hacking with setjmp/longjmp bleeding
through...?]
-Rob
[1] That is, within-Unix-process multiprogramming -- what CMUCL *should*
have called it, not "multiprocessing". (Which it isn't -- at least,
not yet. Though SBCL...?)
[2] By experimentation(!) I discovered that in CMUCL a
(close stream :abort t) will not do any additional "write()"s, and
thus will not cause additional SIGPIPEs to occur. [A previous version
of the handler that just did (ignore-errors (close stream)) had caused
infinite recursion of SIGPIPEs, since each handler entry would try to
CLOSE and eash CLOSE would try to flush the stream, which would cause
a "write()" and... Oops.]
-----
Rob Warnock, PP-ASEL-IA <rpw3@rpw3.org>
627 26th Avenue <URL:http://rpw3.org/>
San Mateo, CA 94403 (650)572-2607