Skip to content

Commit 3b3e358

Browse files
committed
Split ZMQ related code from jupyter-server.el
In the following, accept means moved from `jupyter-server.el` and move means moved to `jupyter-server-ioloop-comm.el`. A label like [class] means the references that appear before the label are actually methods of class. * jupyter-server.el: Move all `jupyter-event-handler` methods. (jupyter-server-kernel-comm): Add `ws` slot. The class has been repurposed for communication using a websocket in the current Emacs instance. The old behavior has been assigned to the `jupyter-server-ioloop-kernel-comm` class. (jupyter-comm-id, jupyter-server-name-client-kernel) (jupyter-channel-alive-p, jupyter-channels-running-p) (jupyter-server-kernel-manager, jupyter-current-server): Rename `jupyter-server-kernel-comm` to `jupyter-server-abstract-kcomm`. (jupyter-server-kernel-connected-p): Redefine as a generic function. Move the method body. (jupyter-comm-start, jupyter-connect-client) (jupyter-disconnect-client) [jupyter-server]: Move. (jupyter-comm-start, jupyter-comm-stop) (jupyter-send, jupyter-comm-alive-p) [jupyter-server-kernel-comm]: Move. * jupyter-server-ioloop-comm.el: Accept moved `jupyter-event-handler` methods. Rename `jupyter-server` to `jupyter-server-ioloop-comm`. Rename `jupyter-server-kernel-comm` to `jupyter-server-ioloop-kernel-comm`. (jupyter-server-kernel-connected-p) (jupyter-comm-start, jupyter-connect-client) (jupyter-disconnect-client) [jupyter-server]: Accept. (jupyter-comm-start, jupyter-comm-stop) (jupyter-comm-alive-p, jupyter-send) [jupyter-server-kernel-comm]: Accept.
1 parent a5f8d99 commit 3b3e358

File tree

2 files changed

+119
-114
lines changed

2 files changed

+119
-114
lines changed

jupyter-server-ioloop-comm.el

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,114 @@
4040
(defclass jupyter-server-ioloop-kernel-comm (jupyter-server-abstract-kcomm)
4141
())
4242

43+
;;; `jupyter-ioloop-comm' event handlers
44+
45+
(cl-defmethod jupyter-event-handler ((comm jupyter-server-ioloop-comm)
46+
(event (head disconnect-channels)))
47+
(let ((kernel-id (cadr event)))
48+
(with-slots (ioloop) comm
49+
(cl-callf2 remove kernel-id
50+
(process-get (oref ioloop process) :kernel-ids)))))
51+
52+
(cl-defmethod jupyter-event-handler ((comm jupyter-server-ioloop-comm)
53+
(event (head connect-channels)))
54+
(let ((kernel-id (cadr event)))
55+
(with-slots (ioloop) comm
56+
(cl-callf append (process-get (oref ioloop process) :kernel-ids)
57+
(list kernel-id)))))
58+
59+
(cl-defmethod jupyter-event-handler ((comm jupyter-server-ioloop-comm) event)
60+
"Send EVENT to all clients connected to COMM.
61+
Each client must have a KERNEL slot which, in turn, must have an
62+
ID slot. The second element of EVENT is expected to be a kernel
63+
ID. Send EVENT, with the kernel ID excluded, to a client whose
64+
kernel has a matching ID."
65+
(let ((kernel-id (cadr event)))
66+
(setq event (cons (car event) (cddr event)))
67+
(jupyter-comm-handler-loop comm client
68+
(when (equal kernel-id (oref (oref client kernel) id))
69+
;; TODO: Since the event handlers of CLIENT will eventually call the
70+
;; `jupyter-handle-message' of a `jupyter-kernel-client' we really
71+
;; don't need to do any filtering based off of a `jupyter-session-id',
72+
;; but maybe should? The `jupyter-handle-message' method will only
73+
;; handle messages that have a parent ID of a previous request so there
74+
;; already is filtering at the kernel client level.
75+
(jupyter-event-handler client event)))))
76+
77+
;;; `jupyter-ioloop-comm' methods
78+
79+
(cl-defmethod jupyter-comm-start ((comm jupyter-server-ioloop-comm))
80+
(unless (and (slot-boundp comm 'ioloop)
81+
(jupyter-ioloop-alive-p (oref comm ioloop)))
82+
;; TODO: Is a write to the cookie file and then a read of the cookie file
83+
;; whenever connecting a websocket in a subprocess good enough? If, e.g.
84+
;; the notebook is restarted and it clears the login information, there are
85+
;; sometimes error due to `jupyter-api-request' trying to ask for login
86+
;; information which look like "wrong type argument listp, [http://...]".
87+
;; They don't seem to happens with the changes mentioned, but is it enough?
88+
(url-cookie-write-file)
89+
(oset comm ioloop (jupyter-server-ioloop
90+
:url (oref comm url)
91+
:ws-url (oref comm ws-url)
92+
:ws-headers (jupyter-api-auth-headers comm)))
93+
(cl-call-next-method)))
94+
95+
(cl-defmethod jupyter-comm-add-handler ((comm jupyter-server-ioloop-comm)
96+
(kcomm jupyter-server-ioloop-kernel-comm))
97+
(cl-call-next-method)
98+
(with-slots (id) (oref kcomm kernel)
99+
(unless (jupyter-server-kernel-connected-p comm id)
100+
(jupyter-server--connect-channels comm id))))
101+
102+
(cl-defmethod jupyter-comm-remove-handler ((comm jupyter-server-ioloop-comm)
103+
(kcomm jupyter-server-ioloop-kernel-comm))
104+
(with-slots (id) (oref kcomm kernel)
105+
(when (jupyter-server-kernel-connected-p comm id)
106+
(jupyter-send comm 'disconnect-channels id)
107+
(unless (jupyter-ioloop-wait-until (oref comm ioloop)
108+
'disconnect-channels #'identity)
109+
(error "Timeout when disconnecting websocket for kernel id %s" id))))
110+
(cl-call-next-method))
111+
112+
(cl-defmethod jupyter-server-kernel-connected-p ((comm jupyter-server-ioloop-comm) id)
113+
"Return non-nil if COMM has a WebSocket connection to a kernel with ID."
114+
(and (jupyter-comm-alive-p comm)
115+
(member id (process-get (oref (oref comm ioloop) process) :kernel-ids))))
116+
117+
;; `jupyter-server-ioloop-kcomm'
118+
119+
(cl-defmethod jupyter-comm-start ((comm jupyter-server-ioloop-kernel-comm) &rest _ignore)
120+
"Register COMM to receive server events.
121+
If SERVER receives events that have the same kernel ID as the
122+
kernel associated with COMM, then COMM's `jupyter-event-handler'
123+
will receive those events."
124+
(with-slots (server) (oref comm kernel)
125+
(jupyter-comm-add-handler server comm)))
126+
127+
(cl-defmethod jupyter-comm-stop ((comm jupyter-server-ioloop-kernel-comm) &rest _ignore)
128+
"Disconnect COMM from receiving server events."
129+
(jupyter-comm-remove-handler (oref (oref comm kernel) server) comm))
130+
131+
(cl-defmethod jupyter-comm-alive-p ((comm jupyter-server-ioloop-kernel-comm))
132+
"Return non-nil if COMM can receive server events for its associated kernel."
133+
(with-slots (kernel) comm
134+
(and (jupyter-server-kernel-connected-p
135+
(oref kernel server)
136+
(oref kernel id))
137+
(catch 'member
138+
(jupyter-comm-handler-loop (oref kernel server) client
139+
(when (eq client comm)
140+
(throw 'member t)))))))
141+
142+
(cl-defmethod jupyter-send ((comm jupyter-server-ioloop-kernel-comm) event-type &rest event)
143+
"Use COMM to send an EVENT to the server with type, EVENT-TYPE.
144+
SERVER will direct EVENT to the right kernel based on the kernel
145+
ID of the kernel associated with COMM."
146+
(with-slots (kernel) comm
147+
(unless (jupyter-comm-alive-p comm)
148+
(jupyter-comm-start comm))
149+
(apply #'jupyter-send (oref kernel server) event-type (oref kernel id) event)))
150+
43151
(provide 'jupyter-server-ioloop-comm)
44152

45153
;;; jupyter-server-ioloop-comm.el ends here

jupyter-server.el

Lines changed: 11 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
(declare-function jupyter-tramp-file-name-p "jupyter-tramp" (filename))
8484
(declare-function jupyter-tramp-server-from-file-name "jupyter-tramp" (filename))
8585
(declare-function jupyter-tramp-file-name-from-url "jupyter-tramp" (url))
86+
(declare-function jupyter-server-ioloop-kernel-comm "jupyter-server-ioloop-comm")
87+
(declare-function jupyter-server-ioloop-comm "jupyter-server-ioloop-comm")
8688

8789
(defgroup jupyter-server nil
8890
"Support for the Jupyter kernel gateway"
@@ -169,9 +171,9 @@ Access should be done through `jupyter-available-kernelspecs'.")))
169171
:abstract t)
170172

171173
(defclass jupyter-server-kernel-comm (jupyter-server-abstract-kcomm)
172-
())
174+
((ws :type websocket)))
173175

174-
(cl-defmethod jupyter-comm-id ((comm jupyter-server-kernel-comm))
176+
(cl-defmethod jupyter-comm-id ((comm jupyter-server-abstract-kcomm))
175177
(let* ((kernel (oref comm kernel))
176178
(id (oref kernel id)))
177179
(or (jupyter-server-kernel-name (oref kernel server) id)
@@ -250,7 +252,7 @@ CLIENT must be communicating with a `jupyter-server-kernel', the
250252
ID of the kernel will be associated with NAME, see
251253
`jupyter-server-kernel-names'."
252254
(cl-check-type client jupyter-kernel-client)
253-
(cl-check-type (oref client kcomm) jupyter-server-kernel-comm)
255+
(cl-check-type (oref client kcomm) jupyter-server-abstract-kcomm)
254256
(let* ((kernel (thread-first client
255257
(oref kcomm)
256258
(oref kernel)))
@@ -259,42 +261,10 @@ ID of the kernel will be associated with NAME, see
259261

260262
;;; Plumbing
261263

262-
;;;; `jupyter-server' events
263-
264-
(cl-defmethod jupyter-event-handler ((comm jupyter-server)
265-
(event (head disconnect-channels)))
266-
(let ((kernel-id (cadr event)))
267-
(with-slots (ioloop) comm
268-
(cl-callf2 remove kernel-id
269-
(process-get (oref ioloop process) :kernel-ids)))))
270-
271-
(cl-defmethod jupyter-event-handler ((comm jupyter-server)
272-
(event (head connect-channels)))
273-
(let ((kernel-id (cadr event)))
274-
(with-slots (ioloop) comm
275-
(cl-callf append (process-get (oref ioloop process) :kernel-ids)
276-
(list kernel-id)))))
277-
278-
(cl-defmethod jupyter-event-handler ((comm jupyter-server) event)
279-
"Send EVENT to all clients connected to COMM.
280-
Each client must have a KERNEL slot which, in turn, must have an
281-
ID slot. The second element of EVENT is expected to be a kernel
282-
ID. Send EVENT, with the kernel ID excluded, to a client whose
283-
kernel has a matching ID."
284-
(let ((kernel-id (cadr event)))
285-
(setq event (cons (car event) (cddr event)))
286-
(jupyter-comm-handler-loop comm client
287-
(when (equal kernel-id (oref (oref client kernel) id))
288-
;; TODO: Since the event handlers of CLIENT will eventually call the
289-
;; `jupyter-handle-message' of a `jupyter-kernel-client' we really
290-
;; don't need to do any filtering based off of a `jupyter-session-id',
291-
;; but maybe should? The `jupyter-handle-message' method will only
292-
;; handle messages that have a parent ID of a previous request so there
293-
;; already is filtering at the kernel client level.
294-
(jupyter-event-handler client event)))))
295-
296264
;;;; `jupyter-server' methods
297265

266+
(cl-defgeneric jupyter-server-kernel-connected-p ((client jupyter-server) id)
267+
"Return non-nil if CLIENT can communicate with a kernel that has ID.")
298268
(defun jupyter-server--connect-channels (server id)
299269
(jupyter-send server 'connect-channels id)
300270
(jupyter-with-timeout
@@ -326,44 +296,6 @@ with default `jupyter-api-authentication-method'"))
326296
(prog1 (cl-call-next-method)
327297
(jupyter-server--refresh-comm server)))))
328298

329-
(cl-defmethod jupyter-comm-start ((comm jupyter-server))
330-
(unless (and (slot-boundp comm 'ioloop)
331-
(jupyter-ioloop-alive-p (oref comm ioloop)))
332-
;; TODO: Is a write to the cookie file and then a read of the cookie file
333-
;; whenever connecting a websocket in a subprocess good enough? If, e.g.
334-
;; the notebook is restarted and it clears the login information, there are
335-
;; sometimes error due to `jupyter-api-request' trying to ask for login
336-
;; information which look like "wrong type argument listp, [http://...]".
337-
;; They don't seem to happens with the changes mentioned, but is it enough?
338-
(url-cookie-write-file)
339-
(oset comm ioloop (jupyter-server-ioloop
340-
:url (oref comm url)
341-
:ws-url (oref comm ws-url)
342-
:ws-headers (jupyter-api-auth-headers comm)))
343-
(cl-call-next-method)))
344-
345-
(cl-defmethod jupyter-comm-add-handler ((comm jupyter-server)
346-
(kcomm jupyter-server-kernel-comm))
347-
(cl-call-next-method)
348-
(with-slots (id) (oref kcomm kernel)
349-
(unless (jupyter-server-kernel-connected-p comm id)
350-
(jupyter-server--connect-channels comm id))))
351-
352-
(cl-defmethod jupyter-comm-remove-handler ((comm jupyter-server)
353-
(kcomm jupyter-server-kernel-comm))
354-
(with-slots (id) (oref kcomm kernel)
355-
(when (jupyter-server-kernel-connected-p comm id)
356-
(jupyter-send comm 'disconnect-channels id)
357-
(unless (jupyter-ioloop-wait-until (oref comm ioloop)
358-
'disconnect-channels #'identity)
359-
(error "Timeout when disconnecting websocket for kernel id %s" id))))
360-
(cl-call-next-method))
361-
362-
(cl-defmethod jupyter-server-kernel-connected-p ((comm jupyter-server) id)
363-
"Return non-nil if COMM has a WebSocket connection to a kernel with ID."
364-
(and (jupyter-comm-alive-p comm)
365-
(member id (process-get (oref (oref comm ioloop) process) :kernel-ids))))
366-
367299
(defun jupyter-server--verify-kernelspec (server spec)
368300
(cl-destructuring-bind (name _ . kspec) spec
369301
(let ((server-spec (assoc name (jupyter-server-kernelspecs server))))
@@ -400,53 +332,18 @@ The kernelspecs are returned in the same form as returned by
400332
(cons nil (plist-get spec :spec)))))))
401333
(plist-get (oref server kernelspecs) :kernelspecs))
402334

403-
;;;; `jupyter-server-kernel-comm' methods
404-
405-
(cl-defmethod jupyter-comm-start ((comm jupyter-server-kernel-comm) &rest _ignore)
406-
"Register COMM to receive server events.
407-
If SERVER receives events that have the same kernel ID as the
408-
kernel associated with COMM, then COMM's `jupyter-event-handler'
409-
will receive those events."
410-
(with-slots (server) (oref comm kernel)
411-
(jupyter-comm-start server)
412-
(jupyter-comm-add-handler server comm)))
413-
414-
(cl-defmethod jupyter-comm-stop ((comm jupyter-server-kernel-comm) &rest _ignore)
415-
"Disconnect COMM from receiving server events."
416-
(jupyter-comm-remove-handler (oref (oref comm kernel) server) comm))
417-
418-
(cl-defmethod jupyter-send ((comm jupyter-server-kernel-comm) event-type &rest event)
419-
"Use COMM to send an EVENT to the server with type, EVENT-TYPE.
420-
SERVER will direct EVENT to the right kernel based on the kernel
421-
ID of the kernel associated with COMM."
422-
(with-slots (kernel) comm
423-
(unless (jupyter-comm-alive-p comm)
424-
(jupyter-comm-start comm))
425-
(apply #'jupyter-send (oref kernel server) event-type (oref kernel id) event)))
426-
427-
(cl-defmethod jupyter-comm-alive-p ((comm jupyter-server-kernel-comm))
428-
"Return non-nil if COMM can receive server events for its associated kernel."
429-
(with-slots (kernel) comm
430-
(and (jupyter-server-kernel-connected-p
431-
(oref kernel server)
432-
(oref kernel id))
433-
(catch 'member
434-
(jupyter-comm-handler-loop (oref kernel server) client
435-
(when (eq client comm)
436-
(throw 'member t)))))))
437-
438335
;; TODO: Remove the need for these methods, they are remnants from an older
439336
;; implementation. They will need to be removed from `jupyter-kernel-client'.
440-
(cl-defmethod jupyter-channel-alive-p ((comm jupyter-server-kernel-comm) _channel)
337+
(cl-defmethod jupyter-channel-alive-p ((comm jupyter-server-abstract-kcomm) _channel)
441338
(jupyter-comm-alive-p comm))
442339

443-
(cl-defmethod jupyter-channels-running-p ((comm jupyter-server-kernel-comm))
340+
(cl-defmethod jupyter-channels-running-p ((comm jupyter-server-abstract-kcomm))
444341
(jupyter-comm-alive-p comm))
445342

446343
;;;; `jupyter-server-kernel-manager'
447344

448345
(defclass jupyter-server-kernel-manager (jupyter-kernel-manager)
449-
((comm :type jupyter-server-kernel-comm)))
346+
((comm :type jupyter-server-abstract-kcomm)))
450347

451348
(cl-defmethod jupyter-comm-start ((manager jupyter-server-kernel-manager))
452349
"Start a websocket connection to MANAGER's kernel.
@@ -604,7 +501,7 @@ a URL."
604501
((and jupyter-current-client
605502
(object-of-class-p
606503
(oref jupyter-current-client kcomm)
607-
'jupyter-server-kernel-comm)
504+
'jupyter-server-abstract-kcomm)
608505
(thread-first jupyter-current-client
609506
(oref kcomm)
610507
(oref kernel)

0 commit comments

Comments
 (0)