Skip to content

Commit f6ceeaa

Browse files
Merge pull request #490 from martinRenou/workaround_port_issue
Prevent two kernels to have the same ports
2 parents dc213de + 3279597 commit f6ceeaa

File tree

1 file changed

+57
-4
lines changed

1 file changed

+57
-4
lines changed

jupyter_client/multikernelmanager.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77

88
import os
99
import uuid
10+
import socket
1011

1112
import zmq
1213

1314
from traitlets.config.configurable import LoggingConfigurable
1415
from ipython_genutils.importstring import import_item
1516
from traitlets import (
16-
Instance, Dict, Unicode, Any, DottedObjectName, observe
17+
Instance, Dict, Unicode, Any, DottedObjectName, observe, default
1718
)
1819
from ipython_genutils.py3compat import unicode_type
1920

@@ -55,13 +56,53 @@ class MultiKernelManager(LoggingConfigurable):
5556
"""
5657
)
5758

59+
def __init__(self, *args, **kwargs):
60+
super(MultiKernelManager, self).__init__(*args, **kwargs)
61+
62+
# Cache all the currently used ports
63+
self.currently_used_ports = set()
64+
5865
@observe('kernel_manager_class')
5966
def _kernel_manager_class_changed(self, change):
60-
self.kernel_manager_factory = import_item(change['new'])
67+
self.kernel_manager_factory = self._create_kernel_manager_factory()
6168

6269
kernel_manager_factory = Any(help="this is kernel_manager_class after import")
70+
71+
@default('kernel_manager_factory')
6372
def _kernel_manager_factory_default(self):
64-
return import_item(self.kernel_manager_class)
73+
return self._create_kernel_manager_factory()
74+
75+
def _create_kernel_manager_factory(self):
76+
kernel_manager_ctor = import_item(self.kernel_manager_class)
77+
78+
def create_kernel_manager(*args, **kwargs):
79+
km = kernel_manager_ctor(*args, **kwargs)
80+
81+
if km.transport == 'tcp':
82+
km.shell_port = self._find_available_port(km.ip)
83+
km.iopub_port = self._find_available_port(km.ip)
84+
km.stdin_port = self._find_available_port(km.ip)
85+
km.hb_port = self._find_available_port(km.ip)
86+
km.control_port = self._find_available_port(km.ip)
87+
88+
return km
89+
90+
return create_kernel_manager
91+
92+
def _find_available_port(self, ip):
93+
while True:
94+
tmp_sock = socket.socket()
95+
tmp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
96+
tmp_sock.bind((ip, 0))
97+
port = tmp_sock.getsockname()[1]
98+
tmp_sock.close()
99+
100+
# This is a workaround for https://github.com/jupyter/jupyter_client/issues/487
101+
# We prevent two kernels to have the same ports.
102+
if port not in self.currently_used_ports:
103+
self.currently_used_ports.add(port)
104+
105+
return port
65106

66107
context = Instance('zmq.Context')
67108
def _context_default(self):
@@ -113,7 +154,6 @@ def start_kernel(self, kernel_name=None, **kwargs):
113154
self._kernels[kernel_id] = km
114155
return kernel_id
115156

116-
@kernel_method
117157
def shutdown_kernel(self, kernel_id, now=False, restart=False):
118158
"""Shutdown a kernel by its kernel uuid.
119159
@@ -127,8 +167,21 @@ def shutdown_kernel(self, kernel_id, now=False, restart=False):
127167
Will the kernel be restarted?
128168
"""
129169
self.log.info("Kernel shutdown: %s" % kernel_id)
170+
171+
kernel = self.get_kernel(kernel_id)
172+
173+
ports = (
174+
kernel.shell_port, kernel.iopub_port, kernel.stdin_port,
175+
kernel.hb_port, kernel.control_port
176+
)
177+
178+
kernel.shutdown_kernel(now=now, restart=restart)
130179
self.remove_kernel(kernel_id)
131180

181+
if not restart and kernel.transport == 'tcp':
182+
for port in ports:
183+
self.currently_used_ports.remove(port)
184+
132185
@kernel_method
133186
def request_shutdown(self, kernel_id, restart=False):
134187
"""Ask a kernel to shut down by its kernel uuid"""

0 commit comments

Comments
 (0)