Folks, apologies in advance for the long post; this is an unusual
problem, and after scouring gmane and the cmucl documentation as well
as other newsgroups w/o finding an answer, I thought I'd ask here, and
note all the details.
I have a long-running process doing various file I/O (all on the local
server) going in an infinite loop:
(defun reload ()
(loop
(unwind-protect
(when (> (- (get-universal-time) *load-interval*) *last-load*)
(setf *last-load* (get-universal-time))
(do-various-io-stuff))
(sleep 600))))
[In case you're wondering, *load-interval* is a parameter set to 3600
(one hour), but it can be changed as needed -- this process runs under
detachtty, so I can always go in and increase/reduce it at any time.]
I've made that process -- and its other public interface functions --
available to other lisp processes using the wire package, like this:
(defconstant +wire-port+ *port-number*)
(defvar *process-wire* nil)
(defun start-wire-listener ()
(setf *process-wire*
(handler-case
(wire:create-request-server +wire-port+)
(error nil))))
(defun stop-wire-listener ()
(when *process-wire*
(handler-case
(wire:destroy-request-server *process-wire*)
(error nil))))
(defun start-my-server ()
(mp:make-process #'(lambda ()
(start-wire-listener)
(reload))
:name "Server Reloading"))
i.e. When the server starts, (start-my-server) is invoked, which kicks
off the (reload) infinite loop thread.
Client processes (which are all running on the same machine) connect
to it using this code:
(defconstant +my-server-host+ "127.0.0.1")
(defconstant +my-server-port+ *port-number*)
(defvar *my-server-wire* nil)
(defun attach-to-my-server ()
(when (null *my-server-wire*)
(setf *my-server-wire*
(handler-case
(wire:connect-to-remote-server +my-server-host+
+my-server-port+ #'attach-to-my-server)
(error nil)))))
(defmacro with-my-wire (&body body)
`(handler-case
(wire:remote-value *my-server-wire* ,@body)
(error nil)))
i.e. Client processes call (attach-to-my-server) once at the
beginning, then access the public functions of the server process
using the (with-my-wire) macro.
It works well, and runs for days at a time exactly as expected -- the
same (single) server process is up continuously, while each of the
client processes start (off a scheduler), connect to the server
process, make their (with-my-wire) calls, and exit.
Every 3, 4 days or so, however, the server process reports a "bogus
handler" error.
The first restart, REMOVE-THEM, clears the problem, and the server
goes back functioning normally.
But the backtrace (in full, below) makes no sense to me; I cannot
recognize those as any of my functions, nor from where my code started
the error.
I found this function, to clear bogus handlers, suggested elsewhere:
(defun clear-bogus-handlers ()
"Invoke to prevent the (LISP::HANDLER-DESCRIPTORS-ERROR) condition
and dropping into the debugger on error conditions."
(dolist (h lisp::*descriptor-handlers*)
(when (> (lisp::handler-descriptor h) 2)
(sys:remove-fd-handler h))))
and I've applied it to my access macro like this:
(defmacro with-my-wire (&body body)
`(handler-case
(wire:remote-value *my-server-wire* ,@body)
(error nil (clear-bogus-handlers))))
But that does not solve the problem; the same "bogus handler" errors
continue to occur.
Here is the full backtrace from the server process.
Can anyone suggest where the error handling logic should be?
Error in function LISP::HANDLER-DESCRIPTORS-ERROR:
NIL have bad file descriptors.
[Condition of type SIMPLE-ERROR]
Restarts:
0: [REMOVE-THEM] Remove bogus handlers.
1: [RETRY-THEM ] Retry bogus handlers.
2: [CONTINUE ] Go on, leaving handlers marked as bogus.
3: [ABORT ] Return to Top-Level.
Debug (type H for help)
(LISP::HANDLER-DESCRIPTORS-ERROR)
Source: Error finding source:
Error in function DEBUG::GET-FILE-TOP-LEVEL-FORM: Source file no longer exists:
target:code/serve-event.lisp.
0] backtrace
0: (LISP::HANDLER-DESCRIPTORS-ERROR)
1: (LISP::SUB-SERVE-EVENT 1 0)
2: (SYSTEM:WAIT-UNTIL-FD-USABLE 0 :INPUT NIL)
3: (LISP::DO-INPUT #<Stream for Standard Input>)
4: (LISP::INPUT-CHARACTER #<Stream for Standard Input> NIL (LISP::*EOF*))
5: (LISP::SYNONYM-IN #<Synonym Stream to SYSTEM:*STDIN*> NIL (LISP::*EOF*))
6: (LISP::TWO-WAY-IN
#<Two-Way Stream, Input = #<Synonym Stream to SYSTEM:*STDIN*>,
Output = #<Synonym Stream to SYSTEM:*STDOUT*>>
NIL
(LISP::*EOF*))
7: (READ-CHAR
#<Two-Way Stream, Input = #<Synonym Stream to SYSTEM:*STDIN*>,
Output = #<Synonym Stream to SYSTEM:*STDOUT*>>
NIL
(LISP::*EOF*)
NIL)
8: (LISP::READ-PRESERVING-WHITESPACE-INTERNAL
#<Two-Way Stream, Input = #<Synonym Stream to SYSTEM:*STDIN*>,
Output = #<Synonym Stream to SYSTEM:*STDOUT*>>
NIL
(:EOF)
T)
9: (LISP::READ-PRESERVING-WHITESPACE-INTERNAL
#<Two-Way Stream, Input = #<Synonym Stream to SYSTEM:*STDIN*>,
Output = #<Synonym Stream to SYSTEM:*STDOUT*>>
NIL
(:EOF)
NIL)
10: (LISP::READ-PRESERVING-WHITESPACE-INTERNAL 4
#<Two-Way Stream, Input
= #<Synonym Stream to SYSTEM:*STDIN*>, Output = #<Synonym Stream to
SYSTEM:*STDOUT*>>
NIL
(:EOF)
...)[:EXTERNAL]
11: (LISP::READ-INTERNAL
#<Two-Way Stream, Input = #<Synonym Stream to SYSTEM:*STDIN*>,
Output = #<Synonym Stream to SYSTEM:*STDOUT*>>
NIL
(:EOF)
NIL)
12: (READ
#<Two-Way Stream, Input = #<Synonym Stream to SYSTEM:*STDIN*>,
Output = #<Synonym Stream to SYSTEM:*STDOUT*>>
NIL
(:EOF)
NIL)
13: (LISP::%TOP-LEVEL)
14: ((LABELS LISP::RESTART-LISP
SAVE-LISP))
0] 0
0
0
*