Skip to content

Commit f8fa9f9

Browse files
committed
Add support for async kernel starts
1 parent 680fda9 commit f8fa9f9

File tree

2 files changed

+85
-15
lines changed

2 files changed

+85
-15
lines changed

jupyter_client/manager.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import time
1414

1515
import zmq
16-
16+
from concurrent.futures import Future
17+
from tornado import gen
1718
from ipython_genutils.importstring import import_item
1819
from .localinterfaces import is_local_ip, local_ips
1920
from traitlets import (
@@ -192,6 +193,15 @@ def _launch_kernel(self, kernel_cmd, **kw):
192193
"""
193194
return launch_kernel(kernel_cmd, **kw)
194195

196+
@gen.coroutine
197+
def launch_kernel_async(self, kernel_cmd, **kw):
198+
"""actually launch the kernel
199+
200+
override in a subclass to launch kernel subprocesses differently
201+
"""
202+
res = yield gen.maybe_future(launch_kernel(kernel_cmd, **kw))
203+
raise gen.Return(res)
204+
195205
# Control socket used for polite kernel shutdown
196206

197207
def _connect_control_socket(self):
@@ -205,8 +215,8 @@ def _close_control_socket(self):
205215
self._control_socket.close()
206216
self._control_socket = None
207217

208-
def start_kernel(self, **kw):
209-
"""Starts a kernel on this host in a separate process.
218+
def pre_start_kernel(self, **kw):
219+
"""Prepares a kernel for startup in a separate process.
210220
211221
If random ports (port=0) are being used, this method must be called
212222
before the channels are created.
@@ -243,14 +253,53 @@ def start_kernel(self, **kw):
243253
env.update(self.kernel_spec.env or {})
244254
elif self.extra_env:
245255
env.update(self.extra_env)
256+
kw['env'] = env
246257

247-
# launch the kernel subprocess
248-
self.log.debug("Starting kernel: %s", kernel_cmd)
249-
self.kernel = self._launch_kernel(kernel_cmd, env=env,
250-
**kw)
258+
return kernel_cmd, kw
259+
260+
def post_start_kernel(self, **kw):
251261
self.start_restarter()
252262
self._connect_control_socket()
253263

264+
def start_kernel(self, **kw):
265+
"""Starts a kernel on this host in a separate process.
266+
267+
If random ports (port=0) are being used, this method must be called
268+
before the channels are created.
269+
270+
Parameters
271+
----------
272+
`**kw` : optional
273+
keyword arguments that are passed down to build the kernel_cmd
274+
and launching the kernel (e.g. Popen kwargs).
275+
"""
276+
kernel_cmd, kw = self.pre_start_kernel(**kw)
277+
278+
# launch the kernel subprocess
279+
self.log.debug("Starting kernel: %s", kernel_cmd)
280+
self.kernel = self._launch_kernel(kernel_cmd, **kw)
281+
self.post_start_kernel(**kw)
282+
283+
@gen.coroutine
284+
def start_kernel_async(self, **kw):
285+
"""Starts a kernel in a separate process in an asynchronous manner.
286+
287+
If random ports (port=0) are being used, this method must be called
288+
before the channels are created.
289+
290+
Parameters
291+
----------
292+
`**kw` : optional
293+
keyword arguments that are passed down to build the kernel_cmd
294+
and launching the kernel (e.g. Popen kwargs).
295+
"""
296+
kernel_cmd, kw = self.pre_start_kernel(**kw)
297+
298+
# launch the kernel subprocess
299+
self.log.debug("Starting kernel (async): %s", kernel_cmd)
300+
self.kernel = yield self.launch_kernel_async(kernel_cmd, **kw)
301+
self.post_start_kernel(**kw)
302+
254303
def request_shutdown(self, restart=False):
255304
"""Send a shutdown request via control channel
256305
"""

jupyter_client/multikernelmanager.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import zmq
1212

13+
from concurrent.futures import Future
14+
from tornado import gen
1315
from traitlets.config.configurable import LoggingConfigurable
1416
from ipython_genutils.importstring import import_item
1517
from traitlets import (
@@ -82,14 +84,8 @@ def __len__(self):
8284
def __contains__(self, kernel_id):
8385
return kernel_id in self._kernels
8486

85-
def start_kernel(self, kernel_name=None, **kwargs):
86-
"""Start a new kernel.
87-
88-
The caller can pick a kernel_id by passing one in as a keyword arg,
89-
otherwise one will be generated using new_kernel_id().
90-
91-
The kernel ID for the newly started kernel is returned.
92-
"""
87+
def pre_start_kernel(self, kernel_name=None, **kwargs):
88+
"""Prepare kernel manager for startup """
9389
kernel_id = kwargs.pop('kernel_id', self.new_kernel_id(**kwargs))
9490
if kernel_id in self:
9591
raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
@@ -107,10 +103,35 @@ def start_kernel(self, kernel_name=None, **kwargs):
107103
parent=self, log=self.log, kernel_name=kernel_name,
108104
**constructor_kwargs
109105
)
106+
return kernel_id, km
107+
108+
def start_kernel(self, kernel_name=None, **kwargs):
109+
"""Start a new kernel.
110+
111+
The caller can pick a kernel_id by passing one in as a keyword arg,
112+
otherwise one will be generated using new_kernel_id().
113+
114+
The kernel ID for the newly started kernel is returned.
115+
"""
116+
kernel_id, km = self.pre_start_kernel(kernel_name, **kwargs)
110117
km.start_kernel(**kwargs)
111118
self._kernels[kernel_id] = km
112119
return kernel_id
113120

121+
@gen.coroutine
122+
def start_kernel_async(self, kernel_name=None, **kwargs):
123+
"""Start a new kernel asynchronously.
124+
125+
The caller can pick a kernel_id by passing one in as a keyword arg,
126+
otherwise one will be generated using new_kernel_id().
127+
128+
The kernel ID for the newly started kernel is returned.
129+
"""
130+
kernel_id, km = self.pre_start_kernel(kernel_name, **kwargs)
131+
yield km.start_kernel_async(**kwargs)
132+
self._kernels[kernel_id] = km
133+
raise gen.Return(kernel_id)
134+
114135
@kernel_method
115136
def shutdown_kernel(self, kernel_id, now=False, restart=False):
116137
"""Shutdown a kernel by its kernel uuid.

0 commit comments

Comments
 (0)