diff --git a/guix-jupyter-container.scm b/guix-jupyter-container.scm index 29ad0578c0908efdd554240846b907615c3270fc..92def48bcee08701d7d5cc640282827eb2877b90 100644 --- a/guix-jupyter-container.scm +++ b/guix-jupyter-container.scm @@ -83,13 +83,14 @@ ;; Execution. ;; -(define (socket->notebook socket key) - "Create a \"fake\" notebook that's reachable over SOCKET and uses the given +(define (socket->kernel socket key) + "Create a \"fake\" kernel that's reachable over SOCKET and uses the given KEY for signing." ;; XXX: This is really a hack. - (notebook key - #:shell socket - #:iopub socket)) + (kernel "fake-kernel" (getpid) + #:key key + #:shell socket + #:iosub socket)) (define (local-eval socket message count) (let* ((scontent (json-string->scm (message-content message))) @@ -171,10 +172,10 @@ KEY for signing." counter)))) (define (reply-html-to-kernel socket message html count) - ;; XXX: Here we have to create a fake notebook. bah. - (let* ((notebook (socket->notebook socket notebook-key)) - (count (reply-html notebook message html count))) - (send-message notebook + ;; XXX: Here we have to create a fake kernel. bah. + (let* ((kernel (socket->kernel socket notebook-key)) + (count (reply-html kernel message html count))) + (send-message kernel (reply message "guix-end-of-eval" (scm->json-string `(("status" . "ok"))))) count)) @@ -235,15 +236,15 @@ KEY for signing." (if kernel (begin (relay-message (kernel-shell kernel) - (socket->notebook kernel notebook-key) + (socket->kernel kernel notebook-key) (string->utf8 name) message) (values kernels (+ count 1))) (let* ((new-kernel (run-kernel context name (string->utf8 name) notebook-key)) - (notebook (socket->notebook socket notebook-key)) + (kernel (socket->kernel socket notebook-key)) (new-kernels (register-kernel kernels new-kernel))) - (relay-message (kernel-shell new-kernel) notebook + (relay-message (kernel-shell new-kernel) kernel (string->utf8 name) message) (values new-kernels (+ count 1)))))) ((magic-html? code) diff --git a/guix-jupyter-kernel.scm b/guix-jupyter-kernel.scm index 149d83cd526e6aae0348b05eb559a7c633e6f538..fa5476ed36247c9e142842b4d528c657bfad616c 100644 --- a/guix-jupyter-kernel.scm +++ b/guix-jupyter-kernel.scm @@ -36,6 +36,7 @@ (guix-kernel hmac) (guix-kernel magic) (guix-kernel environ) + (guix-kernel jupyter-server) (guix-kernel jupyter-client)) (define session-id (random (* 255 255) @@ -91,33 +92,33 @@ ;; Handlers. ;; -(define (heartbeat-handler notebook) - (let ((message (zmq-message-receive-bytevector (notebook-heartbeat notebook) +(define (heartbeat-handler kernel) + (let ((message (zmq-message-receive-bytevector (kernel-heartbeat kernel) (zmq-msg-init)))) - (zmq-message-send-bytevector (notebook-heartbeat notebook) message)) - (heartbeat-handler notebook)) + (zmq-message-send-bytevector (kernel-heartbeat kernel) message)) + (heartbeat-handler kernel)) -(define (general-handler notebook containers count) - (let* ((message (read-message notebook)) +(define (general-handler kernel containers count) + (let* ((message (read-message kernel)) (handler (dispatch (message-type message)))) - (pub-busy notebook message) + (pub-busy kernel message) (let-values (((containers count) - (handler notebook message containers count))) - (pub-idle notebook message) - (general-handler notebook containers count)))) + (handler kernel message containers count))) + (pub-idle kernel message) + (general-handler kernel containers count)))) ;; Unknown request type, ignore it. -(define (ignore-request notebook message containers count) +(define (ignore-request kernel message containers count) (values containers count)) ;; Send kernel-info. -(define (reply-kernel-info-request notebook message containers count) - (send-message notebook (reply message "kernel_info_reply" +(define (reply-kernel-info-request kernel message containers count) + (send-message kernel (reply message "kernel_info_reply" (scm->json-string KERNEL-INFO))) (values containers count)) -(define (reply-execute-request notebook message containers count) +(define (reply-execute-request kernel message containers count) (let* ((content (message-content message)) (code (hash-ref (json-string->scm content) "code")) (magic (get-magic-line code))) @@ -130,16 +131,16 @@ (env (list-cdr-ref list 4))) (match (proxy-by-name containers env-name) (#f - (let* ((id (start-container notebook env-name env)) + (let* ((id (start-container kernel env-name env)) (container (new-container-connect env-name id)) (new (register-proxy containers env-name container))) - (relay-message (proxy-socket container) notebook + (relay-message (proxy-socket container) kernel (string->utf8 env-name) message) (values new (+ count 1)))) ((? proxy? proxy) - (relay-message (proxy-socket proxy) notebook + (relay-message (proxy-socket proxy) kernel (string->utf8 env-name) message) (values containers (+ count 1)))))) @@ -148,28 +149,28 @@ (let* ((list (string-split magic #\ )) (env-name (list-ref list 2)) (proxy (proxy-by-name containers env-name))) - (relay-message (proxy-socket proxy) notebook + (relay-message (proxy-socket proxy) kernel (string->utf8 env-name) message) (values containers (+ count 1)))) ((magic-html? code) (values containers - (reply-html notebook message + (reply-html kernel message (delete-magic code) count))) (else (relay-message (proxy-socket (proxy-by-name containers "default")) - notebook (string->utf8 "default") + kernel (string->utf8 "default") message) (values containers (+ count 1))))) (λ error (values containers - (reply-html notebook message + (reply-html kernel message (error->html error) count)))))) -(define (shutdown notebook message containers count) - (kill-containers containers notebook message) +(define (shutdown kernel message containers count) + (kill-containers containers kernel message) (exit #t)) ;; @@ -194,7 +195,7 @@ ;; Process exit and signals handler. ;; -(define (kill-containers containers notebook message) +(define (kill-containers containers kernel message) "Terminate all the proxies listed in the CONTAINERS vhash, sending them MESSAGE." (vlist-for-each (match-lambda @@ -202,7 +203,7 @@ MESSAGE." (format (current-error-port) "terminating proxy ~s (PID ~s)...~%" name (proxy-pid proxy)) - (relay-message (proxy-socket proxy) notebook + (relay-message (proxy-socket proxy) kernel (string->utf8 name) message) (zmq-close-socket (proxy-socket proxy)) (false-if-exception (kill (proxy-pid proxy) SIGTERM)))) @@ -238,7 +239,7 @@ MESSAGE." fs))) %network-file-mappings)) -(define (start-container notebook name env) +(define (start-container kernel name env) (let* ((guile-version (if (null? env) (guile-current-version->path) (if (not (package-in-list->path env "guile")) @@ -259,7 +260,7 @@ MESSAGE." (list "--no-auto-compile" "-s" container-path name (number->string session-id) - (notebook-key notebook)))))) + (utf8->string (kernel-key kernel))))))) (root (string-append "/tmp/guix-kernel/container/" name "-" (number->string session-id))) (fs (cons* %immutable-store @@ -276,22 +277,22 @@ MESSAGE." %namespaces 1 exec))) -(define (start-kernel pid notebook) +(define (start-kernel pid kernel) (let ((default-container (alist->vhash `(("default" . ,(new-container-connect "default" pid)))))) - (general-handler notebook default-container 0))) + (general-handler kernel default-container 0))) -(define (exit-handler notebook) +(define (exit-handler kernel) (lambda _ - (close-notebook notebook) + (close-kernel kernel) (exit 1))) ;; Start! -(let ((notebook (call-with-input-file (car (last-pair (command-line))) - json->notebook))) - (sigaction SIGTERM (exit-handler notebook)) - (sigaction SIGINT (exit-handler notebook)) - (start-kernel (start-container notebook "default" '()) - notebook)) +(let ((kernel (call-with-input-file (car (last-pair (command-line))) + json->kernel))) + (sigaction SIGTERM (exit-handler kernel)) + (sigaction SIGINT (exit-handler kernel)) + (start-kernel (start-container kernel "default" '()) + kernel)) diff --git a/guix-kernel/jupyter-client.scm b/guix-kernel/jupyter-client.scm index bdce0e8f877da4616cd942cffe9cc6215822a7f8..f543d1da497b84cf68cf8ba46aa07c57d0b54ba0 100644 --- a/guix-kernel/jupyter-client.scm +++ b/guix-kernel/jupyter-client.scm @@ -18,6 +18,7 @@ (define-module (guix-kernel jupyter-client) #:use-module (guix-kernel hmac) #:use-module (guix-kernel tools) + #:use-module (guix-kernel jupyter-server) #:use-module (simple-zmq) #:use-module (json) #:use-module (srfi srfi-9) @@ -27,17 +28,7 @@ #:export (guile-version KERNEL-INFO - json->notebook - notebook? - notebook - notebook-key - notebook-control - notebook-shell - notebook-standard-input - notebook-heartbeat - notebook-iopub - - close-notebook + json->kernel message make-message reply @@ -73,24 +64,10 @@ ;; Kernel information. ;; -;; Notebook connections. -(define-record-type <notebook> - (%notebook key control shell stdin heartbeat iopub) - notebook? - (key notebook-key) ;bytevector - (control notebook-control) ;zmq socket - (shell notebook-shell) ;zmq socket - (stdin notebook-standard-input) ;zmq socket - (heartbeat notebook-heartbeat) ;zmq socket - (iopub notebook-iopub)) ;zmq socket - -(define* (notebook key #:key control shell standard-input heartbeat iopub) - "Return a new notebook with the given key and the given sockets." - (%notebook key control shell standard-input heartbeat iopub)) - -(define (json->notebook port) - "Read metadata from PORT, typically in the format passed to kernels on the -command line, and return a <notebook> record." +(define* (json->kernel port #:key (name "guix")) + "Read the contents of a Jupyter \"connection file\" from PORT and return a +<kernel> record." + ;; See <https://jupyter-client.readthedocs.io/en/stable/kernels.html#connection-files>. (let* ((table (json->scm port)) (ip (hash-ref table "ip")) (scheme (hash-ref table "transport")) @@ -106,26 +83,18 @@ command line, and return a <notebook> record." (zmq-bind-socket socket uri) socket)) - (notebook key - #:control - (make-socket ZMQ_ROUTER (hash-ref table "control_port")) - #:shell - (make-socket ZMQ_ROUTER (hash-ref table "shell_port")) - #:standard-input - (make-socket ZMQ_ROUTER (hash-ref table "stdin_port")) - #:heartbeat - (make-socket ZMQ_REP (hash-ref table "hb_port")) - #:iopub - (make-socket ZMQ_PUB (hash-ref table "iopub_port"))))) - -(define (close-notebook notebook) - "Close all the open connections of NOTEBOOK." - (for-each zmq-close-socket - (map (lambda (proc) - (proc notebook)) - (list notebook-control notebook-shell - notebook-standard-input - notebook-heartbeat notebook-iopub)))) + (kernel name (getpid) + #:key (string->utf8 key) + #:control + (make-socket ZMQ_ROUTER (hash-ref table "control_port")) + #:shell + (make-socket ZMQ_ROUTER (hash-ref table "shell_port")) + #:standard-input + (make-socket ZMQ_ROUTER (hash-ref table "stdin_port")) + #:heartbeat + (make-socket ZMQ_REP (hash-ref table "hb_port")) + #:iosub + (make-socket ZMQ_PUB (hash-ref table "iopub_port"))))) ;; Jupyter message header as defined at ;; <https://jupyter-client.readthedocs.io/en/stable/messaging.html#general-message-format>. @@ -257,8 +226,8 @@ This is a low-level procedure for internal use." #:parent-header (string->header parent-header) #:metadata metadata)))) -(define (read-message notebook) - "Read a message for NOTEBOOK--i.e., a message sent by Jupyter--and return +(define (read-message kernel) + "Read a message for KERNEL--i.e., a message sent by Jupyter--and return it or #f if nothing is available." ;; FIXME Use 'zmq_poll'. (define (waiting-data socket next) @@ -271,8 +240,8 @@ it or #f if nothing is available." (and next (waiting-data next socket))) (else #f))))) - (and=> (waiting-data (notebook-shell notebook) - (notebook-control notebook)) + (and=> (waiting-data (kernel-shell kernel) + (kernel-control kernel)) parts->message)) (define guile-version (effective-version)) @@ -299,23 +268,23 @@ it or #f if nothing is available." ;; Send procedures. ;; -(define* (send-message notebook message +(define* (send-message kernel message #:key - (notebook-socket notebook-shell) + (kernel-socket kernel-shell) (recipient (and=> (message-parent-header message) header-sender))) - "Send message over the shell socket of NOTEBOOK to RECIPIENT, a ZeroMQ + "Send message over the shell socket of KERNEL to RECIPIENT, a ZeroMQ identity (bytevector)." - (zmq-send-msg-parts-bytevector (notebook-socket notebook) + (zmq-send-msg-parts-bytevector (kernel-socket kernel) (message-parts message - (notebook-key notebook) + (kernel-key kernel) #:recipient recipient))) -(define* (relay-message socket notebook name request) +(define* (relay-message socket kernel name request) "Relay REQUEST, a message, over SOCKET, with recipient NAME (a bytevector representing a ZeroMQ identity), and return its reply. Send replies to -NOTEBOOK." - (let ((parts (message-parts request (notebook-key notebook) +KERNEL." + (let ((parts (message-parts request (kernel-key kernel) #:recipient name))) (zmq-send-msg-parts-bytevector socket parts)) @@ -325,19 +294,19 @@ NOTEBOOK." (sender (message-sender request))) (match (message-type reply) ("execute_reply" - (send-message notebook reply + (send-message kernel reply #:recipient sender) (loop)) ((or "execute_result" "error") - (send-message notebook reply #:notebook-socket notebook-iopub + (send-message kernel reply #:kernel-socket kernel-iopub #:recipient sender) (loop)) ("execute_input" - (send-message notebook reply #:notebook-socket notebook-iopub + (send-message kernel reply #:kernel-socket kernel-iopub #:recipient sender) (loop)) ("stream" - (send-message notebook reply #:notebook-socket notebook-iopub + (send-message kernel reply #:kernel-socket kernel-iopub #:recipient sender) (loop)) ("status" @@ -348,23 +317,23 @@ NOTEBOOK." (_ reply))))) -(define (pub notebook message status) +(define (pub kernel message status) (let ((content `(("execution_state" . ,status)))) - (send-message notebook (reply message "status" + (send-message kernel (reply message "status" (scm->json-string content)) - #:notebook-socket notebook-iopub))) + #:kernel-socket kernel-iopub))) -(define (pub-busy notebook message) - (pub notebook message "busy")) +(define (pub-busy kernel message) + (pub kernel message "busy")) -(define (pub-idle notebook message) - (pub notebook message "idle")) +(define (pub-idle kernel message) + (pub kernel message "idle")) ;; ;; Reply. ;; -(define (reply-html notebook message html count) +(define (reply-html kernel message html count) "Reply to MESSAGE with HTML." (let ((code (hash-ref (json-string->scm (message-content message)) "code")) @@ -372,19 +341,19 @@ NOTEBOOK." (counter (+ count 1)) ;execution counter (send (lambda (socket type content) - (send-message notebook + (send-message kernel (reply message type (scm->json-string content)) - #:notebook-socket socket)))) + #:kernel-socket socket)))) - (send notebook-iopub + (send kernel-iopub "execute_input" `(("code" . ,code) ("execution_count" . ,counter))) - (send notebook-iopub + (send kernel-iopub "execute_result" `(("data" . (("text/html" . ,html))) ("metadata" . ,empty-object) ("execution_count" . ,counter))) - (send notebook-shell + (send kernel-shell "execute_reply" `(("status" . "ok") ("execution_count" . ,counter) ("payload" . []) diff --git a/guix-kernel/jupyter-server.scm b/guix-kernel/jupyter-server.scm index 423a09c6e40423cd7cfa86ee3fc0a0c5eece099a..a9bb4e9a540474045cc2df6ac43cf30d83fda405 100644 --- a/guix-kernel/jupyter-server.scm +++ b/guix-kernel/jupyter-server.scm @@ -35,31 +35,48 @@ kernel? kernel-name kernel-pid + kernel-key kernel-control kernel-shell kernel-standard-input kernel-heartbeat - kernel-iosub)) + kernel-iosub + kernel-iopub ;alias + + close-kernel)) ;; ;; Kernel execution. ;; -;; Type of a proxied Jupyter kernel. +;; Type of a Jupyter kernel. (define-record-type <kernel> - (%kernel name pid control shell stdin heartbeat iosub) + (%kernel name pid key control shell stdin heartbeat iosub) kernel? (name kernel-name) ;string (pid kernel-pid) ;integer + (key kernel-key) ;bytevector (control kernel-control) ;zmq socket (shell kernel-shell) ;zmq socket (stdin kernel-standard-input) ;zmq socket (heartbeat kernel-heartbeat) ;zmq socket (iosub kernel-iosub)) ;zmq socket -(define* (kernel name pid #:key control shell +(define-syntax kernel-iopub (identifier-syntax kernel-iosub)) + +(define* (kernel name pid #:key key control shell standard-input heartbeat iosub) - (%kernel name pid control shell standard-input heartbeat iosub)) + (%kernel name pid key + control shell standard-input heartbeat iosub)) + +(define (close-kernel kernel) + "Close all the open connections of KERNEL." + (for-each zmq-close-socket + (map (lambda (proc) + (proc kernel)) + (list kernel-control kernel-shell + kernel-standard-input + kernel-heartbeat kernel-iopub)))) (define (jupyter-kernel-path) "Return the default search path for Jupyter kernels."