1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-04-30 11:50:28 +02:00
guile/module/system/repl/server.scm
David Thompson b0a3149955 Add cooperative REPL server module.
Modified-by: Mark H Weaver <mhw@netris.org>

* module/system/repl/coop-server.scm: New module.

* module/system/repl/repl.scm (start-repl): Extract body to start-repl*.
  (start-repl*): New procedure.
  (run-repl): Extract body to run-repl*.
  (run-repl*): New procedure.

* module/system/repl/server.scm (run-server): Extract body to
  run-server*.
  (run-server*): New procedure.

* doc/ref/api-evaluation.texi (Cooperative REPL Servers): New node.

* module/Makefile.am (SYSTEM_SOURCES): Add system/repl/coop-server.scm.
2014-02-12 11:26:22 -05:00

164 lines
5.8 KiB
Scheme

;;; Repl server
;; Copyright (C) 2003, 2010, 2011, 2014 Free Software Foundation, Inc.
;; This library is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public
;; License as published by the Free Software Foundation; either
;; version 3 of the License, or (at your option) any later version.
;;
;; This library is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this library; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
;; 02110-1301 USA
;;; Code:
(define-module (system repl server)
#:use-module (system repl repl)
#:use-module (ice-9 threads)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:export (make-tcp-server-socket
make-unix-domain-server-socket
run-server
spawn-server
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 sockets-lock (make-mutex))
;; WARNING: it is unsafe to call 'close-socket!' from another thread.
;; Note: although not exported, this is used by (system repl coop-server)
(define (close-socket! s)
(with-mutex sockets-lock
(set! *open-sockets* (assq-remove! *open-sockets* s)))
;; Close-port could block or raise an exception flushing buffered
;; output. Hmm.
(close-port s))
;; Note: although not exported, this is used by (system repl coop-server)
(define (add-open-socket! s force-close)
(with-mutex sockets-lock
(set! *open-sockets* (acons s force-close *open-sockets*))))
(define (stop-server-and-clients!)
(cond
((with-mutex sockets-lock
(match *open-sockets*
(() #f)
(((s . force-close) . rest)
(set! *open-sockets* rest)
force-close)))
=> (lambda (force-close)
(force-close)
(stop-server-and-clients!)))))
(define* (make-tcp-server-socket #:key
(host #f)
(addr (if host (inet-aton host) INADDR_LOOPBACK))
(port 37146))
(let ((sock (socket PF_INET SOCK_STREAM 0)))
(setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
(bind sock AF_INET addr port)
sock))
(define* (make-unix-domain-server-socket #:key (path "/tmp/guile-socket"))
(let ((sock (socket PF_UNIX SOCK_STREAM 0)))
(setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
(bind sock AF_UNIX path)
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)))
(run-server* server-socket serve-client))
;; Note: although not exported, this is used by (system repl coop-server)
(define (run-server* server-socket serve-client)
;; 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)
(catch #t
(lambda ()
(let ((ready-ports (car (select monitored-ports '() '()))))
;; If we've been asked to shut down, return #f.
(and (not (memq shutdown-read-pipe ready-ports))
(accept server-socket))))
(lambda k-args
(let ((err (system-error-errno k-args)))
(cond
((memv err errs-to-retry)
(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)
(add-open-socket! server-socket shutdown-server)
(listen server-socket 5)
(let lp ((client (accept-new-client)))
;; If client is false, we are shutting down.
(if client
(let ((client-socket (car client))
(client-addr (cdr client)))
(make-thread serve-client client-socket client-addr)
(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)))
(make-thread run-server server-socket))
(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
(lambda ()
(parameterize ((current-input-port client)
(current-output-port client)
(current-error-port client)
(current-warning-port client))
(with-fluids ((*repl-stack* '()))
(start-repl))))))