| 1 | ;;; -*- Log: code.log; Package: wire -*-
|
|---|
| 2 | ;;;
|
|---|
| 3 | ;;; **********************************************************************
|
|---|
| 4 | ;;; This code was written as part of the CMU Common Lisp project at
|
|---|
| 5 | ;;; Carnegie Mellon University, and has been placed in the public domain.
|
|---|
| 6 | ;;;
|
|---|
| 7 | #+NIL
|
|---|
| 8 | (ext:file-comment
|
|---|
| 9 | "$Header$")
|
|---|
| 10 | ;;;
|
|---|
| 11 | ;;; **********************************************************************
|
|---|
| 12 | ;;;
|
|---|
| 13 | ;;; This file implements a simple remote procedure call mechanism on top
|
|---|
| 14 | ;;; of wire.lisp.
|
|---|
| 15 | ;;;
|
|---|
| 16 | ;;; Written by William Lott.
|
|---|
| 17 | ;;;
|
|---|
| 18 |
|
|---|
| 19 | (defpackage :hemlock.wire
|
|---|
| 20 | (:use :common-lisp))
|
|---|
| 21 |
|
|---|
| 22 | (in-package :hemlock.wire)
|
|---|
| 23 |
|
|---|
| 24 | (defstruct remote-wait
|
|---|
| 25 | value1 value2 value3 value4 value5
|
|---|
| 26 | abort
|
|---|
| 27 | finished)
|
|---|
| 28 |
|
|---|
| 29 | (defvar *pending-returns* nil
|
|---|
| 30 | "AList of wire . remote-wait structs")
|
|---|
| 31 |
|
|---|
| 32 | ;;; MAYBE-NUKE-REMOTE-WAIT -- internal
|
|---|
| 33 | ;;;
|
|---|
| 34 | ;;; If the remote wait has finished, remove the external translation.
|
|---|
| 35 | ;;; Otherwise, mark the remote wait as finished so the next call to
|
|---|
| 36 | ;;; MAYBE-NUKE-REMOTE-WAIT will really nuke it.
|
|---|
| 37 | ;;;
|
|---|
| 38 | (defun maybe-nuke-remote-wait (remote)
|
|---|
| 39 | (cond ((remote-wait-finished remote)
|
|---|
| 40 | (forget-remote-translation remote)
|
|---|
| 41 | t)
|
|---|
| 42 | (t
|
|---|
| 43 | (setf (remote-wait-finished remote)
|
|---|
| 44 | t)
|
|---|
| 45 | nil)))
|
|---|
| 46 |
|
|---|
| 47 | ;;; REMOTE -- public
|
|---|
| 48 | ;;;
|
|---|
| 49 | ;;; Execute the body remotly. Subforms are executed locally in the lexical
|
|---|
| 50 | ;;; envionment of the macro call. No values are returned.
|
|---|
| 51 | ;;;
|
|---|
| 52 | (defmacro remote (wire-form &body forms)
|
|---|
| 53 | "Evaluates the given forms remotly. No values are returned, as the remote
|
|---|
| 54 | evaluation is asyncronus."
|
|---|
| 55 | (let ((wire (gensym)))
|
|---|
| 56 | `(let ((,wire ,wire-form))
|
|---|
| 57 | ,@(mapcar #'(lambda (form)
|
|---|
| 58 | `(wire-output-funcall ,wire
|
|---|
| 59 | ',(car form)
|
|---|
| 60 | ,@(cdr form)))
|
|---|
| 61 | forms)
|
|---|
| 62 | (values))))
|
|---|
| 63 |
|
|---|
| 64 | ;;; REMOTE-VALUE-BIND -- public
|
|---|
| 65 | ;;;
|
|---|
| 66 | ;;; Send to remote forms. First, a call to the correct dispatch routine based
|
|---|
| 67 | ;;; on the number of args, then the actual call. The dispatch routine will get
|
|---|
| 68 | ;;; the second funcall and fill in the correct number of arguments.
|
|---|
| 69 | ;;; Note: if there are no arguments, we don't even wait for the function to
|
|---|
| 70 | ;;; return, cause we can kind of guess at what the currect results would be.
|
|---|
| 71 | ;;;
|
|---|
| 72 | (defmacro remote-value-bind (wire-form vars form &rest body)
|
|---|
| 73 | "Bind vars to the multiple values of form (which is executed remotly). The
|
|---|
| 74 | forms in body are only executed if the remote function returned as apposed
|
|---|
| 75 | to aborting due to a throw."
|
|---|
| 76 | (cond
|
|---|
| 77 | ((null vars)
|
|---|
| 78 | `(progn
|
|---|
| 79 | (remote ,wire-form ,form)
|
|---|
| 80 | ,@body))
|
|---|
| 81 | (t
|
|---|
| 82 | (let ((remote (gensym))
|
|---|
| 83 | (wire (gensym)))
|
|---|
| 84 | `(let* ((,remote (make-remote-wait))
|
|---|
| 85 | (,wire ,wire-form)
|
|---|
| 86 | (*pending-returns* (cons (cons ,wire ,remote)
|
|---|
| 87 | *pending-returns*)))
|
|---|
| 88 | (unwind-protect
|
|---|
| 89 | (let ,vars
|
|---|
| 90 | (remote ,wire
|
|---|
| 91 | (,(case (length vars)
|
|---|
| 92 | (1 'do-1-value-call)
|
|---|
| 93 | (2 'do-2-value-call)
|
|---|
| 94 | (3 'do-3-value-call)
|
|---|
| 95 | (4 'do-4-value-call)
|
|---|
| 96 | (5 'do-5-value-call)
|
|---|
| 97 | (t 'do-n-value-call))
|
|---|
| 98 | (make-remote-object ,remote))
|
|---|
| 99 | ,form)
|
|---|
| 100 | (wire-force-output ,wire)
|
|---|
| 101 | (loop
|
|---|
| 102 | #+:hemlock.serve-event
|
|---|
| 103 | (serve-all-events)
|
|---|
| 104 | #-:hemlock.serve-event
|
|---|
| 105 | (wire-get-object ,wire)
|
|---|
| 106 | (when (remote-wait-finished ,remote)
|
|---|
| 107 | (return)))
|
|---|
| 108 | (unless (remote-wait-abort ,remote)
|
|---|
| 109 | ,(case (length vars)
|
|---|
| 110 | (1 `(setf ,(first vars) (remote-wait-value1 ,remote)))
|
|---|
| 111 | (2 `(setf ,(first vars) (remote-wait-value1 ,remote)
|
|---|
| 112 | ,(second vars) (remote-wait-value2 ,remote)))
|
|---|
| 113 | (3 `(setf ,(first vars) (remote-wait-value1 ,remote)
|
|---|
| 114 | ,(second vars) (remote-wait-value2 ,remote)
|
|---|
| 115 | ,(third vars) (remote-wait-value3 ,remote)))
|
|---|
| 116 | (4 `(setf ,(first vars) (remote-wait-value1 ,remote)
|
|---|
| 117 | ,(second vars) (remote-wait-value2 ,remote)
|
|---|
| 118 | ,(third vars) (remote-wait-value3 ,remote)
|
|---|
| 119 | ,(fourth vars) (remote-wait-value4 ,remote)))
|
|---|
| 120 | (5 `(setf ,(first vars) (remote-wait-value1 ,remote)
|
|---|
| 121 | ,(second vars) (remote-wait-value2 ,remote)
|
|---|
| 122 | ,(third vars) (remote-wait-value3 ,remote)
|
|---|
| 123 | ,(fourth vars) (remote-wait-value4 ,remote)
|
|---|
| 124 | ,(fifth vars) (remote-wait-value5 ,remote)))
|
|---|
| 125 | (t
|
|---|
| 126 | (do ((remaining-vars vars (cdr remaining-vars))
|
|---|
| 127 | (form (list 'setf)
|
|---|
| 128 | (nconc form
|
|---|
| 129 | (list (car remaining-vars)
|
|---|
| 130 | `(pop values)))))
|
|---|
| 131 | ((null remaining-vars)
|
|---|
| 132 | `(let ((values (remote-wait-value1 ,remote)))
|
|---|
| 133 | ,form)))))
|
|---|
| 134 | ,@body))
|
|---|
| 135 | (maybe-nuke-remote-wait ,remote)))))))
|
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 138 | ;;; REMOTE-VALUE -- public
|
|---|
| 139 | ;;;
|
|---|
| 140 | ;;; Alternate interface to getting the single return value of a remote
|
|---|
| 141 | ;;; function. Works pretty much just the same, except the single value is
|
|---|
| 142 | ;;; returned.
|
|---|
| 143 | ;;;
|
|---|
| 144 | (defmacro remote-value (wire-form form &optional
|
|---|
| 145 | (on-server-unwind
|
|---|
| 146 | `(error "Remote server unwound")))
|
|---|
| 147 | "Execute the single form remotly. The value of the form is returned.
|
|---|
| 148 | The optional form on-server-unwind is only evaluated if the server unwinds
|
|---|
| 149 | instead of returning."
|
|---|
| 150 | (let ((remote (gensym))
|
|---|
| 151 | (wire (gensym)))
|
|---|
| 152 | `(let* ((,remote (make-remote-wait))
|
|---|
| 153 | (,wire ,wire-form)
|
|---|
| 154 | (*pending-returns* (cons (cons ,wire ,remote)
|
|---|
| 155 | *pending-returns*)))
|
|---|
| 156 | (unwind-protect
|
|---|
| 157 | (progn
|
|---|
| 158 | (remote ,wire
|
|---|
| 159 | (do-1-value-call (make-remote-object ,remote))
|
|---|
| 160 | ,form)
|
|---|
| 161 | (wire-force-output ,wire)
|
|---|
| 162 | (loop
|
|---|
| 163 | #+:hemlock.serve-event
|
|---|
| 164 | (serve-all-events)
|
|---|
| 165 | #-:hemlock.serve-event
|
|---|
| 166 | (wire-get-object ,wire)
|
|---|
| 167 | (when (remote-wait-finished ,remote)
|
|---|
| 168 | (return))))
|
|---|
| 169 | (maybe-nuke-remote-wait ,remote))
|
|---|
| 170 | (if (remote-wait-abort ,remote)
|
|---|
| 171 | ,on-server-unwind
|
|---|
| 172 | (remote-wait-value1 ,remote)))))
|
|---|
| 173 |
|
|---|
| 174 | ;;; DEFINE-FUNCTIONS -- internal
|
|---|
| 175 | ;;;
|
|---|
| 176 | ;;; Defines two functions, one that the client runs in the server, and one
|
|---|
| 177 | ;;; that the server runs in the client:
|
|---|
| 178 | ;;;
|
|---|
| 179 | ;;; DO-n-VALUE-CALL -- internal
|
|---|
| 180 | ;;;
|
|---|
| 181 | ;;; Executed by the remote process. Reads the next object off the wire and
|
|---|
| 182 | ;;; sends the value back. Unwind-protect is used to make sure we send something
|
|---|
| 183 | ;;; back so the requestor doesn't hang.
|
|---|
| 184 | ;;;
|
|---|
| 185 | ;;; RETURN-n-VALUE -- internal
|
|---|
| 186 | ;;;
|
|---|
| 187 | ;;; The remote procedure returned the given value, so fill it in the
|
|---|
| 188 | ;;; remote-wait structure. Note, if the requestor has aborted, just throw
|
|---|
| 189 | ;;; the value away.
|
|---|
| 190 | ;;;
|
|---|
| 191 | (defmacro define-functions (values)
|
|---|
| 192 | (let ((do-call (intern (format nil "~:@(do-~D-value-call~)" values)))
|
|---|
| 193 | (return-values (intern (format nil "~:@(return-~D-value~:P~)" values)))
|
|---|
| 194 | (vars nil))
|
|---|
| 195 | (dotimes (i values)
|
|---|
| 196 | (push (gensym) vars))
|
|---|
| 197 | (setf vars (nreverse vars))
|
|---|
| 198 | `(progn
|
|---|
| 199 | (defun ,do-call (result)
|
|---|
| 200 | (let (worked ,@vars)
|
|---|
| 201 | (unwind-protect
|
|---|
| 202 | (progn
|
|---|
| 203 | (multiple-value-setq ,vars
|
|---|
| 204 | (wire-get-object *current-wire*))
|
|---|
| 205 | (setf worked t))
|
|---|
| 206 | (if worked
|
|---|
| 207 | (remote *current-wire*
|
|---|
| 208 | (,return-values result ,@vars))
|
|---|
| 209 | (remote *current-wire*
|
|---|
| 210 | (remote-return-abort result)))
|
|---|
| 211 | (wire-force-output *current-wire*))))
|
|---|
| 212 | (defun ,return-values (remote ,@vars)
|
|---|
| 213 | (let ((result (remote-object-value remote)))
|
|---|
| 214 | (unless (maybe-nuke-remote-wait result)
|
|---|
| 215 | ,@(let ((setf-forms nil))
|
|---|
| 216 | (dotimes (i values)
|
|---|
| 217 | (push `(setf (,(intern (format nil
|
|---|
| 218 | "~:@(remote-wait-value~D~)"
|
|---|
| 219 | (1+ i)))
|
|---|
| 220 | result)
|
|---|
| 221 | ,(nth i vars))
|
|---|
| 222 | setf-forms))
|
|---|
| 223 | (nreverse setf-forms))))
|
|---|
| 224 | nil))))
|
|---|
| 225 |
|
|---|
| 226 | (define-functions 1)
|
|---|
| 227 | (define-functions 2)
|
|---|
| 228 | (define-functions 3)
|
|---|
| 229 | (define-functions 4)
|
|---|
| 230 | (define-functions 5)
|
|---|
| 231 |
|
|---|
| 232 |
|
|---|
| 233 | ;;; DO-N-VALUE-CALL -- internal
|
|---|
| 234 | ;;;
|
|---|
| 235 | ;;; For more values then 5, all the values are rolled into a list and passed
|
|---|
| 236 | ;;; back as the first value, so we use RETURN-1-VALUE to return it.
|
|---|
| 237 | ;;;
|
|---|
| 238 | (defun do-n-value-call (result)
|
|---|
| 239 | (let (worked values)
|
|---|
| 240 | (unwind-protect
|
|---|
| 241 | (progn
|
|---|
| 242 | (setf values
|
|---|
| 243 | (multiple-value-list (wire-get-object *current-wire*)))
|
|---|
| 244 | (setf worked t))
|
|---|
| 245 | (if worked
|
|---|
| 246 | (remote *current-wire*
|
|---|
| 247 | (return-1-values result values))
|
|---|
| 248 | (remote *current-wire*
|
|---|
| 249 | (remote-return-abort result)))
|
|---|
| 250 | (wire-force-output *current-wire*))))
|
|---|
| 251 |
|
|---|
| 252 | ;;; REMOTE-RETURN-ABORT -- internal
|
|---|
| 253 | ;;;
|
|---|
| 254 | ;;; The remote call aborted instead of returned.
|
|---|
| 255 | ;;;
|
|---|
| 256 | (defun remote-return-abort (result)
|
|---|
| 257 | (setf result (remote-object-value result))
|
|---|
| 258 | (unless (maybe-nuke-remote-wait result)
|
|---|
| 259 | (setf (remote-wait-abort result) t)))
|
|---|
| 260 |
|
|---|
| 261 | #+:hemlock.serve-event
|
|---|
| 262 | ;;; SERVE-REQUESTS -- internal
|
|---|
| 263 | ;;;
|
|---|
| 264 | ;;; Serve all pending requests on the given wire.
|
|---|
| 265 | ;;;
|
|---|
| 266 | (defun serve-requests (wire on-death)
|
|---|
| 267 | (handler-bind
|
|---|
| 268 | ((wire-eof #'(lambda (condition)
|
|---|
| 269 | (declare (ignore condition))
|
|---|
| 270 | (close (wire-stream wire))
|
|---|
| 271 | #+NILGB(system:invalidate-descriptor (wire-fd wire))
|
|---|
| 272 | #+NILGB(unix:unix-close (wire-fd wire))
|
|---|
| 273 | (dolist (pending *pending-returns*)
|
|---|
| 274 | (when (eq (car pending)
|
|---|
| 275 | wire)
|
|---|
| 276 | (unless (maybe-nuke-remote-wait (cdr pending))
|
|---|
| 277 | (setf (remote-wait-abort (cdr pending))
|
|---|
| 278 | t))))
|
|---|
| 279 | (when on-death
|
|---|
| 280 | (funcall on-death))
|
|---|
| 281 | (return-from serve-requests (values))))
|
|---|
| 282 | (wire-error #'(lambda (condition)
|
|---|
| 283 | (declare (ignore condition))
|
|---|
| 284 | #+NILGB
|
|---|
| 285 | (system:invalidate-descriptor (wire-fd wire)))))
|
|---|
| 286 | (progn #+NILGB loop
|
|---|
| 287 | #+NILGB
|
|---|
| 288 | (unless (wire-listen wire)
|
|---|
| 289 | (return))
|
|---|
| 290 | (wire-get-object wire)))
|
|---|
| 291 | (values))
|
|---|
| 292 |
|
|---|
| 293 | ;;; NEW-CONNECTION -- internal
|
|---|
| 294 | ;;;
|
|---|
| 295 | ;;; Maybe build a new wire and add it to the servers list of fds. If the user
|
|---|
| 296 | ;;; Supplied a function, close the socket if it returns NIL. Otherwise, install
|
|---|
| 297 | ;;; the wire.
|
|---|
| 298 | ;;;
|
|---|
| 299 | (defun new-connection (socket addr on-connect)
|
|---|
| 300 | (let ((wire (make-wire socket))
|
|---|
| 301 | (on-death nil))
|
|---|
| 302 | (if (or (null on-connect)
|
|---|
| 303 | (multiple-value-bind (okay death-fn)
|
|---|
| 304 | (funcall on-connect wire addr)
|
|---|
| 305 | (setf on-death death-fn)
|
|---|
| 306 | okay))
|
|---|
| 307 | #+:hemlock.serve-event
|
|---|
| 308 | (add-fd-handler socket :input
|
|---|
| 309 | #'(lambda (socket)
|
|---|
| 310 | (declare (ignore socket))
|
|---|
| 311 | (serve-requests wire on-death)))
|
|---|
| 312 | #-:hemlock.serve-event
|
|---|
| 313 | (make-process (lambda ()
|
|---|
| 314 | (loop (wire-get-object wire)))
|
|---|
| 315 | :name (format nil "Wire process for ~S." wire))
|
|---|
| 316 | (ext-close-connection socket))))
|
|---|
| 317 |
|
|---|
| 318 | ;;; REQUEST-SERVER structure
|
|---|
| 319 | ;;;
|
|---|
| 320 | ;;; Just a simple handle on the socket and system:serve-event handler that make
|
|---|
| 321 | ;;; up a request server.
|
|---|
| 322 | ;;;
|
|---|
| 323 | (defstruct (request-server
|
|---|
| 324 | (:print-function %print-request-server))
|
|---|
| 325 | socket
|
|---|
| 326 | handler)
|
|---|
| 327 |
|
|---|
| 328 | (defun %print-request-server (rs stream depth)
|
|---|
| 329 | (declare (ignore depth))
|
|---|
| 330 | (print-unreadable-object (rs stream :type t)
|
|---|
| 331 | (format stream "for ~D" (request-server-socket rs))))
|
|---|
| 332 |
|
|---|
| 333 | ;;; CREATE-REQUEST-SERVER -- Public.
|
|---|
| 334 | ;;;
|
|---|
| 335 | ;;; Create a TCP/IP listener on the given port. If anyone tries to connect to
|
|---|
| 336 | ;;; it, call NEW-CONNECTION to do the connecting.
|
|---|
| 337 | ;;;
|
|---|
| 338 | #+:hemlock.serve-event
|
|---|
| 339 | (defun create-request-server (port &optional on-connect)
|
|---|
| 340 | "Create a request server on the given port. Whenever anyone connects to it,
|
|---|
| 341 | call the given function with the newly created wire and the address of the
|
|---|
| 342 | connector. If the function returns NIL, the connection is destroyed;
|
|---|
| 343 | otherwise, it is accepted. This returns a manifestation of the server that
|
|---|
| 344 | DESTROY-REQUEST-SERVER accepts to kill the request server."
|
|---|
| 345 | (let* ((socket (ext-create-inet-listener port))
|
|---|
| 346 | (handler (add-fd-handler socket :input
|
|---|
| 347 | #'(lambda (socket)
|
|---|
| 348 | (multiple-value-bind
|
|---|
| 349 | (newconn addr)
|
|---|
| 350 | (ext-accept-tcp-connection socket)
|
|---|
| 351 | (new-connection newconn addr on-connect))))))
|
|---|
| 352 | (make-request-server :socket socket
|
|---|
| 353 | :handler handler)))
|
|---|
| 354 |
|
|---|
| 355 | #-:hemlock.serve-event
|
|---|
| 356 | (defun create-request-server (port &optional on-connect)
|
|---|
| 357 | "Create a request server on the given port. Whenever anyone connects to it,
|
|---|
| 358 | call the given function with the newly created wire and the address of the
|
|---|
| 359 | connector. If the function returns NIL, the connection is destroyed;
|
|---|
| 360 | otherwise, it is accepted. This returns a manifestation of the server that
|
|---|
| 361 | DESTROY-REQUEST-SERVER accepts to kill the request server."
|
|---|
| 362 | (let* ((socket (ext-create-inet-listener port))
|
|---|
| 363 | (handler (make-process
|
|---|
| 364 | (lambda ()
|
|---|
| 365 | (loop
|
|---|
| 366 | (multiple-value-bind
|
|---|
| 367 | (newconn addr)
|
|---|
| 368 | (ext-accept-tcp-connection socket)
|
|---|
| 369 | (new-connection newconn addr on-connect)))))))
|
|---|
| 370 | (make-request-server :socket socket
|
|---|
| 371 | :handler handler)))
|
|---|
| 372 |
|
|---|
| 373 | ;;; DESTROY-REQUEST-SERVER -- Public.
|
|---|
| 374 | ;;;
|
|---|
| 375 | ;;; Removes the request server from SERVER's list of file descriptors and
|
|---|
| 376 | ;;; closes the socket behind it.
|
|---|
| 377 | ;;;
|
|---|
| 378 | (defun destroy-request-server (server)
|
|---|
| 379 | "Quit accepting connections to the given request server."
|
|---|
| 380 | #+:hemlock.serve-event
|
|---|
| 381 | (remove-fd-handler (request-server-handler server))
|
|---|
| 382 | ;;
|
|---|
| 383 | (ext-close-socket (request-server-socket server))
|
|---|
| 384 | nil)
|
|---|
| 385 |
|
|---|
| 386 | ;;; CONNECT-TO-REMOTE-SERVER -- Public.
|
|---|
| 387 | ;;;
|
|---|
| 388 | ;;; Just like the doc string says, connect to a remote server. A handler is
|
|---|
| 389 | ;;; installed to handle return values, etc.
|
|---|
| 390 | ;;;
|
|---|
| 391 | #-NIL
|
|---|
| 392 | (defun connect-to-remote-server (hostname port &optional on-death)
|
|---|
| 393 | "Connect to a remote request server addressed with the given host and port
|
|---|
| 394 | pair. This returns the created wire."
|
|---|
| 395 | (let* ((socket (ext-connect-to-inet-socket hostname port))
|
|---|
| 396 | (wire (make-wire socket)))
|
|---|
| 397 | #+:hemlock.serve-event
|
|---|
| 398 | ;; hmm, what exactly should this accomplish?
|
|---|
| 399 | (add-fd-handler socket :input
|
|---|
| 400 | #'(lambda (socket)
|
|---|
| 401 | (declare (ignore socket))
|
|---|
| 402 | (serve-requests wire on-death)))
|
|---|
| 403 | wire))
|
|---|