On Mon, 2010-02-08 at 22:31 -0600, Peter Keller wrote:
Hello,
I discovered in my boundary testing of with-open-socket that it sort of has a funny behavior I didn't expect.
Basically, I had a top level code like this in a blocking i/o tcp ipv4 client talking to a server:
(with-open-socket (socket :stuff :things)
;; set up i/o handlers, each wrap their reads and writes around ;; handler-case. A disconnector on the sockets removes the io handlers, but ;; doesn't close the socket since with-open-socket should do it.
(handler-case ;; run forever until empty (meaning we disconnected from the server), ;; then return. (event-dispatch *base*)
(hangup () (format t "Hangup while writing to server~%"))
(end-of-file () (format t "End of file from server~%"))))
Now, what happened was, under certain conditions where there is inflight data on the sockets, a registered handler could get a signaled condition, specifically hangup, that tells me the server went away on a write. This is expected. I disconnect the socket which removes the i/o handlers, which causes event-dispatch to return since there are no registered handlers.... and then bam! The with-open-stream performs a (finish-output) and (close) on the socket and I get _another_ hangup condition!
So I had to rewrite the code like this moving the handler-case up one scope:
(handler-case (with-open-socket (socket :things :stuff) ;; set up i/o handlers, each wrap their reads and writes around ;; handler-case. A disconnector on the sockets removes the io handlers, but ;; doesn't close the socket since with-open-socket should do it. (event-dispatch *base*))
(hangup () (format t "Hangup while writing to server~%"))
(end-of-file () (format t "End of file from server~%"))))
Is this a to be expected scenario in condition handling with respect to with-open-socket? Is this idiomatic code?
This is to be expected. This is what happens: one of the handlers signals a HANGUP(which is Posix EPIPE), the handler-case handles it and returns. At his point, since the code wrapped by with-open-socket returns, w-o-s closes the socket with :abort NIL, which tries again to flush the output buffer(:abort T would just close the file descriptor), which causes another HANGUP to be signaled.
Should the disconnector function close the socket even though with-open-socket also closes it? I'm not sure of the right thing to do here.
My opinion is that it's not a good idea to mix with-open-socket and the event loop because w-o-s is thought for a synchronous data flow. The only exception to that is if you use w-o-s to open the server socket. With active sockets, it's better to close them from within the event loop.