coop-server.scm 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. ;;; Cooperative REPL server
  2. ;; Copyright (C) 2014, 2016 Free Software Foundation, Inc.
  3. ;; This library is free software; you can redistribute it and/or
  4. ;; modify it under the terms of the GNU Lesser General Public
  5. ;; License as published by the Free Software Foundation; either
  6. ;; version 3 of the License, or (at your option) any later version.
  7. ;;
  8. ;; This library is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;; Lesser General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU Lesser General Public
  14. ;; License along with this library; if not, write to the Free Software
  15. ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  16. ;; 02110-1301 USA
  17. ;;; Code:
  18. (define-module (system repl coop-server)
  19. #:use-module (ice-9 match)
  20. #:use-module (ice-9 receive)
  21. #:use-module (ice-9 threads)
  22. #:use-module (ice-9 q)
  23. #:use-module (srfi srfi-9)
  24. #:export (spawn-coop-repl-server
  25. poll-coop-repl-server))
  26. ;; Hack to import private bindings from (system repl repl).
  27. (define-syntax-rule (import-private module sym ...)
  28. (begin
  29. (define sym (@@ module sym))
  30. ...))
  31. (import-private (system repl repl) start-repl* prompting-meta-read)
  32. (import-private (system repl server)
  33. run-server* add-open-socket! close-socket!
  34. make-tcp-server-socket guard-against-http-request)
  35. (define-record-type <coop-repl-server>
  36. (%make-coop-repl-server mutex queue)
  37. coop-repl-server?
  38. (mutex coop-repl-server-mutex)
  39. (queue coop-repl-server-queue))
  40. (define (make-coop-repl-server)
  41. (%make-coop-repl-server (make-mutex) (make-q)))
  42. (define (coop-repl-server-eval coop-server opcode . args)
  43. "Queue a new instruction with the symbolic name OPCODE and an arbitrary
  44. number of arguments, to be processed the next time COOP-SERVER is polled."
  45. (with-mutex (coop-repl-server-mutex coop-server)
  46. (enq! (coop-repl-server-queue coop-server)
  47. (cons opcode args))))
  48. (define-record-type <coop-repl>
  49. (%make-coop-repl mutex condvar thunk cont)
  50. coop-repl?
  51. (mutex coop-repl-mutex)
  52. (condvar coop-repl-condvar) ; signaled when thunk becomes non-#f
  53. (thunk coop-repl-read-thunk set-coop-repl-read-thunk!)
  54. (cont coop-repl-cont set-coop-repl-cont!))
  55. (define (make-coop-repl)
  56. (%make-coop-repl (make-mutex) (make-condition-variable) #f #f))
  57. (define (coop-repl-read coop-repl)
  58. "Read an expression via the thunk stored in COOP-REPL."
  59. (let ((thunk
  60. (with-mutex (coop-repl-mutex coop-repl)
  61. (unless (coop-repl-read-thunk coop-repl)
  62. (wait-condition-variable (coop-repl-condvar coop-repl)
  63. (coop-repl-mutex coop-repl)))
  64. (let ((thunk (coop-repl-read-thunk coop-repl)))
  65. (unless thunk
  66. (error "coop-repl-read: condvar signaled, but thunk is #f!"))
  67. (set-coop-repl-read-thunk! coop-repl #f)
  68. thunk))))
  69. (thunk)))
  70. (define (store-repl-cont cont coop-repl)
  71. "Save the partial continuation CONT within COOP-REPL."
  72. (set-coop-repl-cont! coop-repl
  73. (lambda (exp)
  74. (coop-repl-prompt
  75. (lambda () (cont exp))))))
  76. (define (coop-repl-prompt thunk)
  77. "Apply THUNK within a prompt for cooperative REPLs."
  78. (call-with-prompt 'coop-repl-prompt thunk store-repl-cont))
  79. (define (make-coop-reader coop-repl)
  80. "Return a new procedure for reading user input from COOP-REPL. The
  81. generated procedure passes the responsibility of reading input to
  82. another thread and aborts the cooperative REPL prompt."
  83. (lambda (repl)
  84. (let ((read-thunk
  85. ;; Need to preserve the REPL stack and current module across
  86. ;; threads.
  87. (let ((stack (fluid-ref *repl-stack*))
  88. (module (current-module)))
  89. (lambda ()
  90. (with-fluids ((*repl-stack* stack))
  91. (set-current-module module)
  92. (prompting-meta-read repl))))))
  93. (with-mutex (coop-repl-mutex coop-repl)
  94. (when (coop-repl-read-thunk coop-repl)
  95. (error "coop-reader: read-thunk is not #f!"))
  96. (set-coop-repl-read-thunk! coop-repl read-thunk)
  97. (signal-condition-variable (coop-repl-condvar coop-repl))))
  98. (abort-to-prompt 'coop-repl-prompt coop-repl)))
  99. (define (reader-loop coop-server coop-repl)
  100. "Run an unbounded loop that reads an expression for COOP-REPL and
  101. stores the expression within COOP-SERVER for later evaluation."
  102. (coop-repl-server-eval coop-server 'eval coop-repl
  103. (coop-repl-read coop-repl))
  104. (reader-loop coop-server coop-repl))
  105. (define (poll-coop-repl-server coop-server)
  106. "Poll the cooperative REPL server COOP-SERVER and apply a pending
  107. operation if there is one, such as evaluating an expression typed at the
  108. REPL prompt. This procedure must be called from the same thread that
  109. called spawn-coop-repl-server."
  110. (let ((op (with-mutex (coop-repl-server-mutex coop-server)
  111. (let ((queue (coop-repl-server-queue coop-server)))
  112. (and (not (q-empty? queue))
  113. (deq! queue))))))
  114. (when op
  115. (match op
  116. (('new-repl client)
  117. (start-repl-client coop-server client))
  118. (('eval coop-repl exp)
  119. ((coop-repl-cont coop-repl) exp))))
  120. *unspecified*))
  121. (define (start-coop-repl coop-server)
  122. "Start a new cooperative REPL process for COOP-SERVER."
  123. ;; Calling stop-server-and-clients! from a REPL will cause an
  124. ;; exception to be thrown when trying to read from the socket that has
  125. ;; been closed, so we catch that here.
  126. (false-if-exception
  127. (let ((coop-repl (make-coop-repl)))
  128. (make-thread reader-loop coop-server coop-repl)
  129. (start-repl* (current-language) #f (make-coop-reader coop-repl)))))
  130. (define (run-coop-repl-server coop-server server-socket)
  131. "Start the cooperative REPL server for COOP-SERVER using the socket
  132. SERVER-SOCKET."
  133. (run-server* server-socket (make-coop-client-proc coop-server)))
  134. (define* (spawn-coop-repl-server
  135. #:optional (server-socket (make-tcp-server-socket)))
  136. "Create and return a new cooperative REPL server object, and spawn a
  137. new thread to listen for connections on SERVER-SOCKET. Proper
  138. functioning of the REPL server requires that poll-coop-repl-server be
  139. called periodically on the returned server object."
  140. (let ((coop-server (make-coop-repl-server)))
  141. (make-thread run-coop-repl-server
  142. coop-server
  143. server-socket)
  144. coop-server))
  145. (define (make-coop-client-proc coop-server)
  146. "Return a new procedure that is used to schedule the creation of a new
  147. cooperative REPL for COOP-SERVER."
  148. (lambda (client addr)
  149. (coop-repl-server-eval coop-server 'new-repl client)))
  150. (define (start-repl-client coop-server client)
  151. "Run a cooperative REPL for COOP-SERVER within a prompt. All input
  152. and output is sent over the socket CLIENT."
  153. ;; Add the client to the list of open sockets, with a 'force-close'
  154. ;; procedure that closes the underlying file descriptor. We do it
  155. ;; this way because we cannot close the port itself safely from
  156. ;; another thread.
  157. (add-open-socket! client (lambda () (close-fdes (fileno client))))
  158. (guard-against-http-request client)
  159. (with-continuation-barrier
  160. (lambda ()
  161. (coop-repl-prompt
  162. (lambda ()
  163. (parameterize ((current-input-port client)
  164. (current-output-port client)
  165. (current-error-port client)
  166. (current-warning-port client))
  167. (with-fluids ((*repl-stack* '()))
  168. (save-module-excursion
  169. (lambda ()
  170. (start-coop-repl coop-server)))))
  171. ;; This may fail if 'stop-server-and-clients!' is called,
  172. ;; because the 'force-close' procedure above closes the
  173. ;; underlying file descriptor instead of the port itself.
  174. (false-if-exception
  175. (close-socket! client)))))))