1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-12 06:41:13 +02:00

REPL Server: Fix 'stop-server-and-clients!'.

* module/system/repl/server.scm: Import (ice-9 match) and (srfi srfi-1).
  (*open-sockets*): Add comment.  This is now a list of pairs with a
  'force-close' procedure in the cdr.
  (close-socket!): Add comment noting that it is unsafe to call this
  from another thread.
  (add-open-socket!): Add 'force-close' argument, and put it in the cdr
  of the '*open-sockets*' entry.
  (stop-server-and-clients!): Use 'match'.  Remove the first element
  from *open-sockets* immediately.  Call the 'force-close' procedure
  instead of 'close-socket!'.
  (errs-to-retry): New variable.
  (run-server): Add a pipe, used in the 'force-close' procedure to
  cleanly shut down the server.  Put the server socket into non-blocking
  mode.  Use 'select' to monitor both the server socket and the pipe.
  Don't call 'add-open-socket!' on the client-socket.  Close the pipe
  and the server socket cleanly when we're asked to shut down.
  (serve-client): Call 'add-open-socket!' with a 'force-close' procedure
  that cancels the thread.  Set the thread cleanup handler to call
  'close-socket!', instead of calling it in the main body.

* doc/ref/api-evaluation.texi (REPL Servers): Add a caveat to the manual
  entry for 'stop-servers-and-clients!'.
This commit is contained in:
Mark H Weaver 2014-02-04 12:18:22 -05:00
parent b61025ce0f
commit 5ecc58113a
2 changed files with 78 additions and 24 deletions

View file

@ -1279,6 +1279,10 @@ with no arguments.
@deffn {Scheme Procedure} stop-server-and-clients! @deffn {Scheme Procedure} stop-server-and-clients!
Closes the connection on all running server sockets. Closes the connection on all running server sockets.
Please note that in the current implementation, the REPL threads are
cancelled without unwinding their stacks. If any of them are holding
mutexes or are within a critical section, the results are unspecified.
@end deffn @end deffn
@c Local Variables: @c Local Variables:

View file

@ -22,34 +22,43 @@
(define-module (system repl server) (define-module (system repl server)
#:use-module (system repl repl) #:use-module (system repl repl)
#:use-module (ice-9 threads) #:use-module (ice-9 threads)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:export (make-tcp-server-socket #:export (make-tcp-server-socket
make-unix-domain-server-socket make-unix-domain-server-socket
run-server run-server
spawn-server spawn-server
stop-server-and-clients!)) stop-server-and-clients!))
;; List of pairs of the form (SOCKET . FORCE-CLOSE), where SOCKET is a
;; socket port, and FORCE-CLOSE is a thunk that forcefully shuts down
;; the socket.
(define *open-sockets* '()) (define *open-sockets* '())
(define sockets-lock (make-mutex)) (define sockets-lock (make-mutex))
;; WARNING: it is unsafe to call 'close-socket!' from another thread.
(define (close-socket! s) (define (close-socket! s)
(with-mutex sockets-lock (with-mutex sockets-lock
(set! *open-sockets* (delq! s *open-sockets*))) (set! *open-sockets* (assq-remove! *open-sockets* s)))
;; Close-port could block or raise an exception flushing buffered ;; Close-port could block or raise an exception flushing buffered
;; output. Hmm. ;; output. Hmm.
(close-port s)) (close-port s))
(define (add-open-socket! s) (define (add-open-socket! s force-close)
(with-mutex sockets-lock (with-mutex sockets-lock
(set! *open-sockets* (cons s *open-sockets*)))) (set! *open-sockets* (acons s force-close *open-sockets*))))
(define (stop-server-and-clients!) (define (stop-server-and-clients!)
(cond (cond
((with-mutex sockets-lock ((with-mutex sockets-lock
(and (pair? *open-sockets*) (match *open-sockets*
(car *open-sockets*))) (() #f)
=> (lambda (s) (((s . force-close) . rest)
(close-socket! s) (set! *open-sockets* rest)
force-close)))
=> (lambda (force-close)
(force-close)
(stop-server-and-clients!))))) (stop-server-and-clients!)))))
(define* (make-tcp-server-socket #:key (define* (make-tcp-server-socket #:key
@ -67,37 +76,79 @@
(bind sock AF_UNIX path) (bind sock AF_UNIX path)
sock)) sock))
;; List of errno values from 'select' or 'accept' that should lead to a
;; retry in 'run-server'.
(define errs-to-retry
(delete-duplicates
(filter-map (lambda (name)
(and=> (module-variable the-root-module name)
variable-ref))
'(EINTR EAGAIN EWOULDBLOCK))))
(define* (run-server #:optional (server-socket (make-tcp-server-socket))) (define* (run-server #:optional (server-socket (make-tcp-server-socket)))
;; We use a pipe to notify the server when it should shut down.
(define shutdown-pipes (pipe))
(define shutdown-read-pipe (car shutdown-pipes))
(define shutdown-write-pipe (cdr shutdown-pipes))
;; 'shutdown-server' is called by 'stop-server-and-clients!'.
(define (shutdown-server)
(display #\! shutdown-write-pipe)
(force-output shutdown-write-pipe))
(define monitored-ports
(list server-socket
shutdown-read-pipe))
(define (accept-new-client) (define (accept-new-client)
(catch #t (catch #t
(lambda () (accept server-socket)) (lambda ()
(lambda (k . args) (let ((ready-ports (car (select monitored-ports '() '()))))
(cond ;; If we've been asked to shut down, return #f.
((port-closed? server-socket) (and (not (memq shutdown-read-pipe ready-ports))
;; Shutting down. (accept server-socket))))
#f) (lambda k-args
(else (let ((err (system-error-errno k-args)))
(warn "Error accepting client" k args) (cond
;; Retry after a timeout. ((memv err errs-to-retry)
(sleep 1) (accept-new-client))
(accept-new-client)))))) (else
(warn "Error accepting client" k-args)
;; Retry after a timeout.
(sleep 1)
(accept-new-client)))))))
;; Put the socket into non-blocking mode.
(fcntl server-socket F_SETFL
(logior O_NONBLOCK
(fcntl server-socket F_GETFL)))
(sigaction SIGPIPE SIG_IGN) (sigaction SIGPIPE SIG_IGN)
(add-open-socket! server-socket) (add-open-socket! server-socket shutdown-server)
(listen server-socket 5) (listen server-socket 5)
(let lp ((client (accept-new-client))) (let lp ((client (accept-new-client)))
;; If client is false, we are shutting down. ;; If client is false, we are shutting down.
(if client (if client
(let ((client-socket (car client)) (let ((client-socket (car client))
(client-addr (cdr client))) (client-addr (cdr client)))
(add-open-socket! client-socket)
(make-thread serve-client client-socket client-addr) (make-thread serve-client client-socket client-addr)
(lp (accept-new-client)))))) (lp (accept-new-client)))
(begin (close shutdown-write-pipe)
(close shutdown-read-pipe)
(close server-socket)))))
(define* (spawn-server #:optional (server-socket (make-tcp-server-socket))) (define* (spawn-server #:optional (server-socket (make-tcp-server-socket)))
(make-thread run-server server-socket)) (make-thread run-server server-socket))
(define (serve-client client addr) (define (serve-client client addr)
(let ((thread (current-thread)))
;; Close the socket when this thread exits, even if canceled.
(set-thread-cleanup! thread (lambda () (close-socket! client)))
;; Arrange to cancel this thread to forcefully shut down the socket.
(add-open-socket! client (lambda () (cancel-thread thread))))
(with-continuation-barrier (with-continuation-barrier
(lambda () (lambda ()
(parameterize ((current-input-port client) (parameterize ((current-input-port client)
@ -105,5 +156,4 @@
(current-error-port client) (current-error-port client)
(current-warning-port client)) (current-warning-port client))
(with-fluids ((*repl-stack* '())) (with-fluids ((*repl-stack* '()))
(start-repl))))) (start-repl))))))
(close-socket! client))