Replace jupyter-channel-ioloop-comm internals with new impl.

* jupyter-comm-layer.el
(jupyter-comm-initialize): Remove default method.  This is in
preparation for moving over to classless communication.

* jupyter-channel-ioloop-comm (jupyter-connection): Require.
(jupyter--proxy-channel): New type.
(jupyter--make-channel-group, jupyter--channel-alive-p)
(jupyter--start-channel, jupyter--stop-channel)
(make-jupyter-async-connection): New functions.
(jupyter-channel-ioloop-comm): Remove `ioloop-class` slot, update all
callers. Remove `channels` slot, update all setters and
references. Add `conn` slot which holds a `jupyter-connection`.
(jupyter-comm-initialize): Initialize the `conn` slot to the
connection returned by `make-jupyter-async-connection`.
(jupyter-comm-start, jupyter-comm-stop)
(jupyter-comm-alive-p, jupyter-comm-id, jupyter-channel-alive-p)
(jupyter-stop-channel, jupyter-start-channel): Replace the body of these
functions with their equivalents in `conn`.

* jupyter-kernel-process-manager.el
(jupyter-make-client): Update `jupyter-channel-ioloop-comm` call.

* jupyter-repl.el:
(jupyter-connect-repl): Ditto.

* test/test-helper.el
(initialize-instance) [jupyter-echo-client]: Ditto. Replace setting of
`channel` slot with setting the `conn` clot.
This commit is contained in:
Nathaniel Nicandro 2020-04-22 14:44:42 -05:00
parent 7a11c8f0ac
commit 93eeda42a6
5 changed files with 130 additions and 99 deletions

View file

@ -47,113 +47,151 @@
(require 'jupyter-base)
(require 'jupyter-ioloop-comm)
(require 'jupyter-channel-ioloop)
(require 'jupyter-connection)
;;; New implementation
(cl-defstruct jupyter--proxy-channel endpoint alive-p)
(defun jupyter--make-channel-group (session)
(let ((endpoints (jupyter-session-endpoints session)))
(append
(list 'channel-group t
:hb (make-instance
'jupyter-hb-channel
:session session
:endpoint (plist-get endpoints :hb)))
(cl-loop
for channel in '(:control :shell :iopub :stdin)
collect channel
collect (make-jupyter--proxy-channel
:endpoint (plist-get endpoints channel)
:alive-p nil)))))
(defun jupyter--channel-alive-p (ioloop chgroup channel)
(if (eq channel :hb)
(let ((hb (plist-get chgroup channel)))
(and hb (jupyter-channel-alive-p hb)))
(and ioloop (jupyter-ioloop-alive-p ioloop)
(jupyter--proxy-channel-alive-p
(plist-get chgroup channel)))))
(defun jupyter--start-channel (ioloop chgroup channel)
(unless (jupyter--channel-alive-p ioloop chgroup channel)
(if (eq channel :hb) (jupyter-start-channel (plist-get chgroup channel))
(let ((endpoint (jupyter--proxy-channel-endpoint
(plist-get chgroup channel))))
(jupyter-send ioloop 'start-channel channel endpoint)
;; Verify that the channel starts
(jupyter-with-timeout
(nil jupyter-default-timeout
(error "Channel not started in ioloop subprocess (%s)" channel))
(jupyter--channel-alive-p ioloop chgroup channel))))))
(defun jupyter--stop-channel (ioloop chgroup channel)
(when (jupyter--channel-alive-p ioloop chgroup channel)
(if (eq channel :hb) (jupyter-stop-channel (plist-get chgroup channel))
(jupyter-send ioloop 'stop-channel channel)
;; Verify that the channel stops
(jupyter-with-timeout
(nil jupyter-default-timeout
(error "Channel not stopped in ioloop subprocess (%s)" channel))
(not (jupyter--channel-alive-p ioloop chgroup channel))))))
(defun make-jupyter-async-connection (session handler)
"Send kernel messages asynchronously."
(require 'jupyter-zmq-channel-ioloop)
(let ((channels '(:hb :shell :iopub :stdin))
(chgroup (jupyter--make-channel-group session))
(ioloop (make-instance 'jupyter-zmq-channel-ioloop)))
(jupyter-channel-ioloop-set-session ioloop session)
(make-jupyter-connection
:hb (plist-get chgroup :hb)
:id (lambda ()
(format "session=%s" (truncate-string-to-width
(jupyter-session-id session)
9 nil nil "")))
:start (lambda (&optional channel)
(unless (jupyter-ioloop-alive-p ioloop)
(jupyter-ioloop-start
ioloop (lambda (event)
(pcase (car event)
;; These channel events are from
;; `jupyter-channel-ioloop'
('start-channel
(setf (jupyter--proxy-channel-alive-p
(plist-get chgroup (cadr event)))
t))
('stop-channel
(setf (jupyter--proxy-channel-alive-p
(plist-get chgroup (cadr event)))
nil))
(_
(funcall handler event))))))
(if channel (jupyter--start-channel ioloop chgroup channel)
(cl-loop
for channel in channels
do (jupyter--start-channel ioloop chgroup channel))))
:stop (lambda (&optional channel)
(if channel (jupyter--stop-channel ioloop chgroup channel)
(cl-loop
for channel in channels
do (jupyter--stop-channel ioloop chgroup channel))
(jupyter-ioloop-stop ioloop))
(jupyter-ioloop-stop ioloop))
:send (lambda (&rest event)
(apply #'jupyter-send ioloop event))
:alive-p (lambda (&optional channel)
(if channel (jupyter--channel-alive-p ioloop chgroup channel)
(cl-loop
for channel in channels
thereis (jupyter--channel-alive-p ioloop chgroup channel)))))))
;;; Old implementation
(cl-defstruct jupyter-proxy-channel endpoint alive-p)
(defclass jupyter-channel-ioloop-comm (jupyter-ioloop-comm
jupyter-hb-comm
jupyter-comm-autostop)
((ioloop-class :type class :initarg :ioloop-class)
(session :type jupyter-session)
(channels :type (list-of (or keyword jupyter-proxy-channel)) :initform nil)))
(cl-defmethod initialize-instance ((comm jupyter-channel-ioloop-comm) &optional _slots)
(cl-call-next-method)
(unless (slot-boundp comm 'ioloop-class)
(oset comm ioloop-class 'jupyter-channel-ioloop))
(with-slots (ioloop-class) comm
(jupyter-error-if-not-client-class-p ioloop-class 'jupyter-channel-ioloop)
(oset comm ioloop (make-instance ioloop-class))))
((conn :type jupyter-connection)
(session :type jupyter-session)))
(cl-defmethod jupyter-comm-id ((comm jupyter-channel-ioloop-comm))
(format "session=%s" (truncate-string-to-width
(jupyter-session-id (oref comm session))
9 nil nil "")))
(jupyter-conn-id (oref comm conn)))
(cl-defmethod jupyter-comm-initialize ((comm jupyter-channel-ioloop-comm)
(session jupyter-session))
(cl-call-next-method)
(let ((endpoints (jupyter-session-endpoints session)))
(oset comm session (copy-sequence session))
(oset comm hb (make-instance
'jupyter-hb-channel
:session (oref comm session)
:endpoint (plist-get endpoints :hb)))
(oset comm channels (cl-loop
for channel in '(:stdin :shell :iopub)
collect channel and
collect (make-jupyter-proxy-channel
:endpoint (plist-get endpoints channel)
:alive-p nil)))))
(session jupyter-session))
(oset comm session session)
(let ((conn (make-jupyter-async-connection
session (lambda (event) (jupyter-event-handler comm event)))))
(oset comm conn conn)
(oset comm hb (jupyter-connection-hb (oref comm conn)))))
(cl-defmethod jupyter-comm-start ((comm jupyter-channel-ioloop-comm))
(with-slots (ioloop session) comm
(unless (jupyter-ioloop-alive-p ioloop)
(jupyter-channel-ioloop-set-session ioloop (oref comm session))
(jupyter-ioloop-start
ioloop (lambda (event)
(pcase (car event)
;; These channel events are from `jupyter-channel-ioloop'
('start-channel
(setf (jupyter-proxy-channel-alive-p
(plist-get (oref comm channels) (cadr event)))
t))
('stop-channel
(setf (jupyter-proxy-channel-alive-p
(plist-get (oref comm channels) (cadr event)))
nil))
(_ (jupyter-event-handler comm event))))))
(cl-loop
for channel in '(:hb :shell :iopub :stdin)
do (jupyter-start-channel comm channel))))
(jupyter-start (oref comm conn)))
(cl-defmethod jupyter-comm-stop ((comm jupyter-channel-ioloop-comm))
(cl-loop
for channel in '(:hb :shell :iopub :stdin)
do (jupyter-stop-channel comm channel))
(cl-call-next-method))
(jupyter-stop (oref comm conn)))
(cl-defmethod jupyter-send ((comm jupyter-channel-ioloop-comm) &rest event)
(apply #'jupyter--send (oref comm conn) event))
;;;; Channel querying methods
(cl-defmethod jupyter-comm-alive-p ((comm jupyter-channel-ioloop-comm))
(cl-loop
for channel in '(:shell :iopub :stdin :hb)
thereis (jupyter-channel-alive-p comm channel)))
(jupyter-alive-p (oref comm conn)))
(cl-defmethod jupyter-channel-alive-p ((comm jupyter-channel-ioloop-comm) channel)
(if (eq channel :hb)
(and (slot-boundp comm 'hb)
(jupyter-channel-alive-p (oref comm hb)))
(with-slots (ioloop) comm
(and ioloop (jupyter-ioloop-alive-p ioloop)
(jupyter-proxy-channel-alive-p
(plist-get (oref comm channels) channel))))))
(jupyter-alive-p (oref comm conn) channel))
;;;; Channel start/stop methods
(cl-defmethod jupyter-stop-channel ((comm jupyter-channel-ioloop-comm) channel)
(when (jupyter-channel-alive-p comm channel)
(if (eq channel :hb) (jupyter-stop-channel (oref comm hb))
(with-slots (ioloop) comm
(jupyter-send ioloop 'stop-channel channel)
;; Verify that the channel stops
(jupyter-with-timeout
(nil jupyter-default-timeout
(error "Channel not stopped in ioloop subprocess (%s)" channel))
(not (jupyter-channel-alive-p comm channel)))))))
(jupyter-stop (oref comm conn) channel))
(cl-defmethod jupyter-start-channel ((comm jupyter-channel-ioloop-comm) channel)
(unless (jupyter-channel-alive-p comm channel)
(if (eq channel :hb) (jupyter-start-channel (oref comm hb))
(let ((endpoint (jupyter-proxy-channel-endpoint
(plist-get (oref comm channels) channel))))
(with-slots (ioloop) comm
(jupyter-send ioloop 'start-channel channel endpoint)
;; Verify that the channel starts
(jupyter-with-timeout
(nil jupyter-default-timeout
(error "Channel not started in ioloop subprocess (%s)" channel))
(jupyter-channel-alive-p comm channel)))))))
(jupyter-start (oref comm conn) channel))
(provide 'jupyter-channel-ioloop-comm)

View file

@ -132,11 +132,6 @@ called if needed. This means that a call to
(cl-defgeneric jupyter-comm-initialize ((comm jupyter-comm-layer) &rest _ignore)
"Initialize communication on COMM.")
(cl-defmethod jupyter-comm-initialize ((comm jupyter-comm-layer) &rest _ignore)
"Raise an error if COMM is already alive."
(when (jupyter-comm-alive-p comm)
(error "Can't initialize a live comm")))
;; TODO: Figure out a better interface for these channel methods or just make
;; them unnecessary. The design of `jupyter-comm-layer' only deals with
;; "events" and the channel abstraction is an implementation detail that

View file

@ -225,15 +225,13 @@ connect to MANAGER's kernel."
(with-slots (kernel) manager
(prog1 client
(require 'jupyter-channel-ioloop-comm)
(require 'jupyter-zmq-channel-ioloop)
;; TODO: We can also have the manager hold the kcomm object and just
;; pass a single kcomm object to all clients using this manager since the
;; kcomm broadcasts event to all connected clients. This is more
;; efficient as it only uses one subprocess for every client connected to
;; a kernel.
(oset client kcomm (make-instance
'jupyter-channel-ioloop-comm
:ioloop-class 'jupyter-zmq-channel-ioloop))
'jupyter-channel-ioloop-comm))
(jupyter-comm-initialize client (oref kernel session))))))
(cl-defmethod jupyter-start-kernel :after ((manager jupyter-kernel-process-manager) &rest _args)

View file

@ -2165,10 +2165,8 @@ interactively, DISPLAY the new REPL buffer as well."
(let ((client (make-instance client-class)))
;; FIXME: See note in `jupyter-make-client'
(require 'jupyter-channel-ioloop-comm)
(require 'jupyter-zmq-channel-ioloop)
(oset client kcomm (make-instance
'jupyter-channel-ioloop-comm
:ioloop-class 'jupyter-zmq-channel-ioloop))
'jupyter-channel-ioloop-comm))
(jupyter-comm-initialize client file-or-plist)
(jupyter-start-channels client)
(jupyter-hb-unpause client)

View file

@ -89,14 +89,16 @@ handling a message is always
(cl-defmethod initialize-instance ((client jupyter-echo-client) &optional _slots)
(cl-call-next-method)
(oset client messages (make-ring 10))
(oset client kcomm (jupyter-channel-ioloop-comm
:ioloop-class 'jupyter-zmq-channel-ioloop))
(oset client kcomm (jupyter-channel-ioloop-comm))
(with-slots (kcomm) client
(oset kcomm hb (jupyter-hb-channel))
(oset kcomm channels
(list :stdin (make-jupyter-proxy-channel)
:shell (make-jupyter-proxy-channel)
:iopub (make-jupyter-proxy-channel)))))
(oset kcomm conn
(make-jupyter-connection
:hb (jupyter-hb-channel)
:start #'ignore
:stop #'ignore
:send #'ignore
:alive-p #'ignore))
(oset kcomm hb (jupyter-connection-hb (oref kcomm conn)))))
(cl-defmethod jupyter-send ((client jupyter-echo-client)
channel