2018-01-08 21:38:32 -06:00
|
|
|
;;; jupyter-kernel-manager.el --- Jupyter kernel manager -*- lexical-binding: t -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2018 Nathaniel Nicandro
|
|
|
|
|
|
|
|
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.edu>
|
|
|
|
;; Created: 08 Jan 2018
|
|
|
|
;; Version: 0.0.1
|
|
|
|
;; Keywords:
|
|
|
|
;; X-URL: https://github.com/nathan/jupyter-kernel-manager
|
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or
|
|
|
|
;; modify it under the terms of the GNU General Public License as
|
|
|
|
;; published by the Free Software Foundation; either version 2, or (at
|
|
|
|
;; your option) any later version.
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful, but
|
|
|
|
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
;; General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
|
|
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
|
|
;; Boston, MA 02111-1307, USA.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
2018-01-04 23:03:18 -06:00
|
|
|
(require 'jupyter-base)
|
2018-01-08 21:38:32 -06:00
|
|
|
(require 'jupyter-messages)
|
2018-01-04 23:03:18 -06:00
|
|
|
(require 'jupyter-client)
|
|
|
|
|
2018-01-08 21:38:32 -06:00
|
|
|
(defgroup jupyter-kernel-manager nil
|
|
|
|
"Jupyter kernel manager"
|
2018-01-18 22:43:52 -06:00
|
|
|
:group 'jupyter)
|
2018-01-08 21:38:32 -06:00
|
|
|
|
2018-05-06 23:38:09 -05:00
|
|
|
(defclass jupyter-kernel-manager ()
|
2018-01-04 23:03:18 -06:00
|
|
|
((name
|
|
|
|
:initarg :name
|
2018-01-18 22:43:52 -06:00
|
|
|
:type string
|
|
|
|
:documentation "The name of the kernel that is being managed.")
|
2018-05-06 23:38:09 -05:00
|
|
|
(session
|
|
|
|
:type jupyter-session
|
|
|
|
:initarg :session
|
|
|
|
:documentation "The session object used to sign and
|
|
|
|
send/receive messages.")
|
2018-01-04 23:03:18 -06:00
|
|
|
(conn-file
|
2018-01-18 22:43:52 -06:00
|
|
|
:type (or null string)
|
|
|
|
:documentation "The absolute path of the connection file when
|
|
|
|
the kernel is alive.")
|
2018-01-04 23:03:18 -06:00
|
|
|
(kernel
|
|
|
|
:type (or null process)
|
|
|
|
:initform nil
|
2018-01-18 22:43:52 -06:00
|
|
|
:documentation "The local kernel process when the kernel is
|
|
|
|
alive.")
|
2018-01-04 23:03:18 -06:00
|
|
|
(control-channel
|
2018-02-03 00:02:33 -06:00
|
|
|
:type (or null jupyter-sync-channel)
|
2018-01-04 23:03:18 -06:00
|
|
|
:initform nil
|
2018-01-18 22:43:52 -06:00
|
|
|
:documentation "A control channel to make shutdown and
|
|
|
|
interrupt requests to the kernel.")
|
2018-01-17 20:37:34 -06:00
|
|
|
(spec
|
2018-01-04 23:03:18 -06:00
|
|
|
:type (or null json-plist)
|
2018-05-06 23:38:09 -05:00
|
|
|
:initarg :spec
|
2018-01-18 22:43:52 -06:00
|
|
|
:initform nil
|
|
|
|
:documentation "The kernelspec used to start/restart the kernel.")))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-01-08 22:31:33 -06:00
|
|
|
(cl-defmethod initialize-instance ((manager jupyter-kernel-manager) &rest _slots)
|
2018-01-04 23:03:18 -06:00
|
|
|
"Initialize MANAGER based on SLOTS.
|
|
|
|
If the `:name' slot is not found in SLOTS, it defaults to
|
|
|
|
\"python\". This means that without providing a kernel name, the
|
|
|
|
default kernel is a python kernel."
|
|
|
|
(cl-call-next-method)
|
|
|
|
(unless (slot-boundp manager 'name)
|
|
|
|
(oset manager name "python")))
|
|
|
|
|
2018-01-18 16:06:22 -06:00
|
|
|
(cl-defmethod destructor ((manager jupyter-kernel-manager) &rest _params)
|
|
|
|
"Kill the kernel of MANAGER and stop its channels."
|
|
|
|
;; See `jupyter--kernel-sentinel' for other cleanup
|
|
|
|
(when (processp (oref manager kernel))
|
|
|
|
(delete-process (oref manager kernel)))
|
|
|
|
(jupyter-stop-channels manager))
|
|
|
|
|
2018-01-18 23:00:31 -06:00
|
|
|
(cl-defgeneric jupyter-make-client ((manager jupyter-kernel-manager) class &rest slots)
|
|
|
|
"Make a new client from CLASS connected to MANAGER's kernel.
|
|
|
|
SLOTS are the slots used to initialize the client with.")
|
|
|
|
|
2018-01-04 23:03:18 -06:00
|
|
|
(cl-defmethod jupyter-make-client ((manager jupyter-kernel-manager) class &rest slots)
|
|
|
|
"Make a new client from CLASS connected to MANAGER's kernel.
|
|
|
|
CLASS should be a subclass of `jupyter-kernel-client', a new
|
2018-05-12 14:52:35 -05:00
|
|
|
instance of CLASS is initialized with SLOTS and configured to
|
2018-05-06 23:38:09 -05:00
|
|
|
connect to MANAGER's kernel."
|
2018-01-04 23:03:18 -06:00
|
|
|
(unless (child-of-class-p class 'jupyter-kernel-client)
|
2018-01-06 16:42:55 -06:00
|
|
|
(signal 'wrong-type-argument (list '(subclass jupyter-kernel-client) class)))
|
2018-01-04 23:03:18 -06:00
|
|
|
(let ((client (apply #'make-instance class slots)))
|
2018-05-06 23:38:09 -05:00
|
|
|
(prog1 client
|
|
|
|
(jupyter-initialize-connection client (oref manager session))
|
|
|
|
(oset client manager manager))))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-02-09 17:23:46 -06:00
|
|
|
(defun jupyter--kernel-sentinel (manager kernel _event)
|
2018-01-22 19:21:44 -06:00
|
|
|
"Cleanup resources after kernel shutdown.
|
|
|
|
If MANAGER's KERNEL process terminates, i.e. when EVENT describes
|
|
|
|
an event in which the KERNEL process was killed: kill the process
|
2018-05-12 14:52:35 -05:00
|
|
|
buffer and delete MANAGER's connection file from the
|
|
|
|
`jupyter-runtime-directory'."
|
2018-01-04 23:03:18 -06:00
|
|
|
(cond
|
2018-02-09 17:23:46 -06:00
|
|
|
((not (process-live-p kernel))
|
2018-05-20 11:02:24 -05:00
|
|
|
(when (and (slot-boundp manager 'conn-file)
|
|
|
|
(file-exists-p (oref manager conn-file)))
|
2018-01-14 13:54:02 -06:00
|
|
|
(delete-file (oref manager conn-file)))
|
2018-01-04 23:03:18 -06:00
|
|
|
(oset manager kernel nil)
|
2018-01-13 22:06:41 -06:00
|
|
|
(oset manager conn-file nil))))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-01-13 22:04:06 -06:00
|
|
|
(defun jupyter--start-kernel (manager kernel-name env args)
|
2018-01-04 23:03:18 -06:00
|
|
|
"Start a kernel.
|
2018-01-22 19:21:44 -06:00
|
|
|
For a `jupyter-kernel-manager', MANAGER, state a kernel named
|
|
|
|
KERNEL-NAME with ENV and ARGS.
|
|
|
|
|
|
|
|
If ENV is non-nil, then it should be a plist containing
|
|
|
|
environment variable names as keywords along with their
|
|
|
|
corresponding values. These will be set as the process
|
|
|
|
environment before starting the kernel.
|
|
|
|
|
|
|
|
ARGS should be a list of command line arguments used to start the
|
|
|
|
kernel process. The name of the command used to start the kernel
|
|
|
|
should be the first element of ARGS and the rest of the elements
|
|
|
|
of ARGS are the arguments of the command.
|
2018-01-06 16:42:55 -06:00
|
|
|
|
|
|
|
Return the newly created kernel process."
|
|
|
|
(let* ((process-environment
|
2018-01-04 23:03:18 -06:00
|
|
|
(append
|
|
|
|
;; The first entry takes precedence when duplicated variables
|
|
|
|
;; are found in `process-environment'
|
|
|
|
(cl-loop
|
|
|
|
for e on env by #'cddr
|
|
|
|
for k = (car e)
|
|
|
|
for v = (cadr e)
|
|
|
|
collect (format "%s=%s" (cl-subseq (symbol-name k) 1) v))
|
2018-01-13 22:04:06 -06:00
|
|
|
process-environment))
|
|
|
|
(proc (apply #'start-process
|
|
|
|
(format "jupyter-kernel-%s" kernel-name)
|
2018-01-16 11:51:16 -06:00
|
|
|
(generate-new-buffer
|
|
|
|
(format " *jupyter-kernel[%s]*" kernel-name))
|
|
|
|
(car args) (cdr args))))
|
2018-01-13 22:04:06 -06:00
|
|
|
(prog1 proc
|
|
|
|
(set-process-sentinel
|
|
|
|
proc (apply-partially #'jupyter--kernel-sentinel manager)))))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-01-18 23:00:31 -06:00
|
|
|
(cl-defgeneric jupyter-start-kernel ((manager jupyter-kernel-manager) &optional timeout)
|
|
|
|
"Start a kernel based on MANAGER's slots. Wait until TIMEOUT for startup.")
|
|
|
|
|
2018-01-04 23:03:18 -06:00
|
|
|
;; TODO: Allow passing arguments like a different kernel file name or different
|
|
|
|
;; ports and arguments to the kernel
|
2018-01-12 18:09:30 -06:00
|
|
|
(cl-defmethod jupyter-start-kernel ((manager jupyter-kernel-manager) &optional timeout)
|
2018-01-04 23:03:18 -06:00
|
|
|
"Start a kernel and associate it with MANAGER.
|
|
|
|
|
|
|
|
The MANAGER's `name' property is passed to
|
2018-01-23 13:44:12 -06:00
|
|
|
`jupyter-find-kernelspecs' in order to find the kernel to start.
|
|
|
|
If `jupyter-find-kernelspecs' returns multiple kernelspecs that
|
|
|
|
match `name', the first one on the list is used.
|
2018-01-04 23:03:18 -06:00
|
|
|
|
|
|
|
If a valid kernel is found, its kernelspec is used to start a new
|
|
|
|
kernel. Starting a kernel involves the following steps:
|
|
|
|
|
2018-05-15 23:41:19 -05:00
|
|
|
1. Write the connection info of MANAGER's session to a file in
|
2018-09-09 21:33:05 -05:00
|
|
|
the `jupyter-runtime-directory'.
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-05-15 23:41:19 -05:00
|
|
|
2. Start a kernel subprocess passing the connection info file as
|
2018-01-13 13:40:32 -06:00
|
|
|
the {connection_file} argument in the kernelspec argument
|
2018-05-15 23:41:19 -05:00
|
|
|
vector of the kernel.
|
|
|
|
|
|
|
|
3. Connect the control channel of MANAGER to the kernel."
|
2018-01-22 18:35:25 -06:00
|
|
|
(unless (jupyter-kernel-alive-p manager)
|
2018-05-06 23:38:09 -05:00
|
|
|
(cl-destructuring-bind (kernel-name . (resource-dir . spec))
|
|
|
|
(car (jupyter-find-kernelspecs (oref manager name)))
|
2018-05-20 11:02:24 -05:00
|
|
|
(let* ((temporary-file-directory jupyter-runtime-directory)
|
|
|
|
(session (oref manager session))
|
2018-05-06 23:38:09 -05:00
|
|
|
(conn-info (jupyter-session-conn-info session))
|
2018-05-20 11:02:24 -05:00
|
|
|
(conn-file (make-temp-file "emacs-kernel-" nil ".json"))
|
2018-05-06 23:38:09 -05:00
|
|
|
(reporter (make-progress-reporter
|
|
|
|
(format "Starting %s kernel..." kernel-name))))
|
|
|
|
;; Write the connection info file
|
2018-05-20 11:02:24 -05:00
|
|
|
(let ((json-encoding-pretty-print t))
|
|
|
|
(with-temp-buffer
|
|
|
|
(insert (json-encode-plist conn-info))
|
|
|
|
(write-region (point-min) (point-max) conn-file)))
|
|
|
|
;; This is needed for reliability
|
|
|
|
(sleep-for 0.5)
|
2018-05-06 23:38:09 -05:00
|
|
|
;; Start the process
|
|
|
|
(let ((atime (nth 4 (file-attributes conn-file)))
|
|
|
|
(proc (jupyter--start-kernel
|
|
|
|
manager kernel-name (plist-get spec :env)
|
|
|
|
(cl-loop
|
2018-05-22 21:43:08 -05:00
|
|
|
for arg in (append (plist-get spec :argv) nil)
|
2018-05-06 23:38:09 -05:00
|
|
|
if (equal arg "{connection_file}")
|
|
|
|
collect conn-file
|
|
|
|
else if (equal arg "{resource_dir}")
|
|
|
|
collect resource-dir
|
|
|
|
else collect arg))))
|
2018-05-30 11:32:55 -05:00
|
|
|
(oset manager kernel proc)
|
|
|
|
(oset manager conn-file conn-file)
|
2018-05-06 23:38:09 -05:00
|
|
|
;; TODO: This is not reliable.
|
|
|
|
;;
|
|
|
|
;; Block until the kernel reads the connection file
|
|
|
|
(with-timeout
|
|
|
|
((or timeout 5)
|
|
|
|
(error "Kernel did not read connection file within timeout"))
|
|
|
|
;; TODO: This may fail on some systems see `file-attributes'
|
|
|
|
(while (equal atime (nth 4 (file-attributes conn-file)))
|
|
|
|
(progress-reporter-update reporter)
|
2018-05-30 11:32:55 -05:00
|
|
|
(sleep-for 0 200))
|
|
|
|
(unless (process-live-p proc)
|
|
|
|
(error "Kernel process exited:\n%s"
|
|
|
|
(with-current-buffer (process-buffer proc)
|
|
|
|
(buffer-string)))))
|
2018-05-06 23:38:09 -05:00
|
|
|
(jupyter-start-channels manager)
|
|
|
|
(progress-reporter-done reporter)
|
|
|
|
manager)))))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
|
|
|
(cl-defmethod jupyter-start-channels ((manager jupyter-kernel-manager))
|
|
|
|
"Start a control channel on MANAGER."
|
2018-05-06 23:38:09 -05:00
|
|
|
(let ((session (oref manager session))
|
|
|
|
(channel (oref manager control-channel)))
|
2018-01-17 20:37:34 -06:00
|
|
|
(if channel
|
|
|
|
(unless (jupyter-channel-alive-p channel)
|
2018-05-06 23:38:09 -05:00
|
|
|
(jupyter-start-channel channel :identity (jupyter-session-id session)))
|
|
|
|
(let ((conn-info (jupyter-session-conn-info session)))
|
2018-01-04 23:03:18 -06:00
|
|
|
(oset manager control-channel
|
2018-02-03 00:02:33 -06:00
|
|
|
(jupyter-sync-channel
|
|
|
|
:type :control
|
2018-05-06 23:38:09 -05:00
|
|
|
:session session
|
2018-01-04 23:03:18 -06:00
|
|
|
:endpoint (format "%s://%s:%d"
|
|
|
|
(plist-get conn-info :transport)
|
|
|
|
(plist-get conn-info :ip)
|
|
|
|
(plist-get conn-info :control_port))))
|
|
|
|
(jupyter-start-channels manager)))))
|
|
|
|
|
|
|
|
(cl-defmethod jupyter-stop-channels ((manager jupyter-kernel-manager))
|
|
|
|
"Stop the control channel on MANAGER."
|
2018-01-17 20:37:34 -06:00
|
|
|
(let ((channel (oref manager control-channel)))
|
|
|
|
(when channel
|
|
|
|
(jupyter-stop-channel channel)
|
2018-01-04 23:03:18 -06:00
|
|
|
(oset manager control-channel nil))))
|
|
|
|
|
2018-01-18 23:00:31 -06:00
|
|
|
(cl-defgeneric jupyter-shutdown-kernel ((manager jupyter-kernel-manager) &optional restart timeout)
|
|
|
|
"Shutdown MANAGER's kernel with an optional RESTART.
|
|
|
|
Wait until TIMEOUT before forcibly shutting down the kernel.")
|
|
|
|
|
2018-01-13 22:08:14 -06:00
|
|
|
(cl-defmethod jupyter-shutdown-kernel ((manager jupyter-kernel-manager) &optional restart timeout)
|
2018-01-18 23:00:31 -06:00
|
|
|
"Shutdown MANAGER's kernel with an optional RESTART.
|
|
|
|
If RESTART is non-nil, then restart the kernel after shutdown.
|
|
|
|
First send a shutdown request on the control channel to the
|
|
|
|
kernel. If the kernel has not shutdown within TIMEOUT, forcibly
|
|
|
|
kill the kernel subprocess. After shutdown the MANAGER's control
|
|
|
|
channel is stopped unless RESTART is non-nil."
|
2018-01-04 23:03:18 -06:00
|
|
|
(when (jupyter-kernel-alive-p manager)
|
2018-01-11 12:13:33 -06:00
|
|
|
(let ((session (oref manager session))
|
2018-01-13 22:08:14 -06:00
|
|
|
(sock (oref (oref manager control-channel) socket))
|
|
|
|
(msg (jupyter-message-shutdown-request :restart restart)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send session sock :shutdown-request msg)
|
2018-01-11 12:13:33 -06:00
|
|
|
(with-timeout ((or timeout 1)
|
|
|
|
(delete-process (oref manager kernel))
|
2018-01-16 11:52:10 -06:00
|
|
|
(message "Kernel did not shutdown by request (%s)" (oref manager name)))
|
2018-01-11 12:13:33 -06:00
|
|
|
(while (jupyter-kernel-alive-p manager)
|
|
|
|
(sleep-for 0.01)))
|
2018-01-13 22:08:14 -06:00
|
|
|
(if restart
|
|
|
|
(jupyter-start-kernel manager)
|
|
|
|
(jupyter-stop-channels manager)))))
|
2018-01-08 22:30:00 -06:00
|
|
|
|
2018-01-18 23:00:31 -06:00
|
|
|
(cl-defgeneric jupyter-interrupt-kernel ((manager jupyter-kernel-manager) &optional timeout)
|
|
|
|
"Interrupt MANAGER's kernel.
|
|
|
|
When the kernel has an interrupt mode of \"message\" send an
|
|
|
|
interrupt request and wait until TIMEOUT for a reply.")
|
|
|
|
|
2018-01-08 22:30:00 -06:00
|
|
|
(cl-defmethod jupyter-interrupt-kernel ((manager jupyter-kernel-manager) &optional timeout)
|
2018-01-18 23:00:31 -06:00
|
|
|
"Interrupt MANAGER's kernel.
|
|
|
|
If the kernel's interrupt mode is set to \"message\" send an
|
|
|
|
interrupt request on MANAGER's control channel and wait until
|
|
|
|
TIMEOUT for a reply. Otherwise if the kernel does not specify an
|
|
|
|
interrupt mode, send an interrupt signal to the kernel
|
|
|
|
subprocess."
|
2018-01-17 20:37:34 -06:00
|
|
|
(pcase (plist-get (oref manager spec) :interrupt_mode)
|
2018-01-08 22:30:00 -06:00
|
|
|
("message"
|
|
|
|
(let ((session (oref manager session))
|
2018-01-13 22:10:18 -06:00
|
|
|
(sock (oref (oref manager control-channel) socket))
|
|
|
|
(msg (jupyter-message-interrupt-request)))
|
2018-05-16 11:33:05 -05:00
|
|
|
(jupyter-send session sock :interrupt-request msg)
|
2018-01-16 11:52:10 -06:00
|
|
|
(with-timeout ((or timeout 1)
|
|
|
|
(message "No interrupt reply from kernel (%s)" (oref manager name)))
|
2018-01-08 22:30:00 -06:00
|
|
|
(while (condition-case nil
|
2018-01-13 22:10:18 -06:00
|
|
|
(prog1 nil (jupyter-recv session sock zmq-NOBLOCK))
|
2018-01-08 22:30:00 -06:00
|
|
|
(zmq-EAGAIN t))
|
|
|
|
(sleep-for 0.01)))))
|
2018-01-13 22:10:18 -06:00
|
|
|
(_ (interrupt-process (oref manager kernel) t))))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-01-18 23:00:31 -06:00
|
|
|
(cl-defgeneric jupyter-kernel-alive-p ((manager jupyter-kernel-manager))
|
|
|
|
"Return non-nil if MANAGER's kernel is alive, otherwise return nil.")
|
|
|
|
|
2018-01-04 23:03:18 -06:00
|
|
|
(cl-defmethod jupyter-kernel-alive-p ((manager jupyter-kernel-manager))
|
2018-01-18 23:00:31 -06:00
|
|
|
"Is MANGER's kernel alive?"
|
2018-01-13 22:11:09 -06:00
|
|
|
(when (oref manager kernel)
|
|
|
|
(process-live-p (oref manager kernel))))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-01-06 16:42:55 -06:00
|
|
|
(defun jupyter-start-new-kernel (kernel-name &optional client-class)
|
2018-01-08 18:11:08 -06:00
|
|
|
"Start a managed Jupyter kernel.
|
|
|
|
KERNEL-NAME is the name of the kernel to start. It can also be
|
|
|
|
the prefix of a valid kernel name, in which case the first kernel
|
2018-02-12 11:03:41 -06:00
|
|
|
in `jupyter-available-kernelspecs' that has KERNEL-NAME as a
|
|
|
|
prefix will be used. Optional argument CLIENT-CLASS is a subclass
|
|
|
|
of `jupyer-kernel-client' and will be used to initialize a new
|
|
|
|
client connected to the kernel. CLIENT-CLASS defaults to
|
|
|
|
`jupyter-kernel-client'.
|
2018-01-08 18:11:08 -06:00
|
|
|
|
2018-09-16 21:17:10 -05:00
|
|
|
Return a list (KM KC) where KM is the `jupyter-kernel-manager'
|
|
|
|
that manages the lifetime of the kernel subprocess. KC is a new
|
|
|
|
client connected to the kernel whose class is CLIENT-CLASS. The
|
|
|
|
client is connected to the kernel with all channels listening for
|
|
|
|
messages and the heartbeat channel unpaused. Note that the
|
|
|
|
client's `manager' slot will also be set to the kernel manager
|
|
|
|
instance, see `jupyter-make-client'."
|
2018-05-06 10:51:18 -05:00
|
|
|
(or client-class (setq client-class 'jupyter-kernel-client))
|
2018-01-06 16:42:55 -06:00
|
|
|
(unless (child-of-class-p client-class 'jupyter-kernel-client)
|
|
|
|
(signal 'wrong-type-argument
|
|
|
|
(list '(subclass jupyter-kernel-client) client-class)))
|
2018-05-06 23:38:09 -05:00
|
|
|
|
|
|
|
(let ((match (car (jupyter-find-kernelspecs kernel-name))))
|
|
|
|
(unless match
|
|
|
|
(error "No kernel found that starts with name (%s)" kernel-name))
|
|
|
|
(setq kernel-name (car match))
|
|
|
|
|
|
|
|
(let* ((key (jupyter-new-uuid))
|
|
|
|
(conn-info (jupyter-create-connection-info
|
|
|
|
:kernel-name kernel-name :key key))
|
|
|
|
(session (jupyter-session :key key :conn-info conn-info))
|
|
|
|
(manager (jupyter-kernel-manager
|
|
|
|
:name kernel-name
|
|
|
|
:spec (cddr match)
|
|
|
|
:session session))
|
|
|
|
(client (jupyter-make-client manager client-class))
|
2018-09-16 21:17:10 -05:00
|
|
|
started)
|
2018-05-06 23:38:09 -05:00
|
|
|
(unwind-protect
|
|
|
|
(let (reporter)
|
|
|
|
(jupyter-start-channels client)
|
|
|
|
(jupyter-hb-unpause client)
|
|
|
|
;; Ensure that the necessary hooks to catch the startup message are
|
|
|
|
;; in place before starting the kernel.
|
|
|
|
;;
|
|
|
|
;; NOTE: Startup messages have no parent header, hence the need for
|
|
|
|
;; `jupyter-include-other-output'.
|
|
|
|
(let* ((jupyter-include-other-output t)
|
|
|
|
(cb (lambda (msg)
|
|
|
|
(setq started
|
2018-05-20 11:03:06 -05:00
|
|
|
(jupyter-message-status-starting-p msg)))))
|
2018-05-06 23:38:09 -05:00
|
|
|
(jupyter-add-hook client 'jupyter-iopub-message-hook cb)
|
|
|
|
(jupyter-start-kernel manager 10)
|
|
|
|
(setq reporter (make-progress-reporter "Kernel starting up..."))
|
2018-05-25 21:16:22 -05:00
|
|
|
(with-timeout (5 (message "Kernel did not send startup message"))
|
2018-05-06 23:38:09 -05:00
|
|
|
(while (not started)
|
|
|
|
(progress-reporter-update reporter)
|
|
|
|
(sleep-for 0.02))
|
|
|
|
(progress-reporter-done reporter))
|
|
|
|
(jupyter-remove-hook client 'jupyter-iopub-message-hook cb))
|
2018-09-16 21:17:10 -05:00
|
|
|
;; FIXME: The javascript kernel doesn't seem to
|
|
|
|
;; send the startup message so instead of
|
|
|
|
;; erroring when the kernel does not send a
|
|
|
|
;; startup message, ensure that it responds to
|
|
|
|
;; a kernel info request.
|
|
|
|
(setq started nil
|
|
|
|
started (jupyter-kernel-info client))
|
|
|
|
(list manager client))
|
|
|
|
(unless started
|
2018-05-06 23:38:09 -05:00
|
|
|
(destructor client)
|
|
|
|
(destructor manager))))))
|
2018-01-06 16:42:55 -06:00
|
|
|
|
2018-01-04 23:03:18 -06:00
|
|
|
(provide 'jupyter-kernel-manager)
|
2018-01-08 21:38:32 -06:00
|
|
|
|
|
|
|
;;; jupyter-kernel-manager.el ends here
|