Subject: Re: catch/throw useful for anything?
From: rpw3@rpw3.org (Rob Warnock)
Date: Thu, 26 Jun 2003 07:40:32 -0500
Newsgroups: comp.lang.lisp
Message-ID: <n7udneWczOpddGejXTWc-g@speakeasy.net>
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