7
7
8
8
import os
9
9
import uuid
10
+ import socket
10
11
11
12
import zmq
12
13
13
14
from traitlets .config .configurable import LoggingConfigurable
14
15
from ipython_genutils .importstring import import_item
15
16
from traitlets import (
16
- Instance , Dict , Unicode , Any , DottedObjectName , observe
17
+ Instance , Dict , Unicode , Any , DottedObjectName , observe , default
17
18
)
18
19
from ipython_genutils .py3compat import unicode_type
19
20
@@ -55,13 +56,53 @@ class MultiKernelManager(LoggingConfigurable):
55
56
"""
56
57
)
57
58
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
+
58
65
@observe ('kernel_manager_class' )
59
66
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 ( )
61
68
62
69
kernel_manager_factory = Any (help = "this is kernel_manager_class after import" )
70
+
71
+ @default ('kernel_manager_factory' )
63
72
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
65
106
66
107
context = Instance ('zmq.Context' )
67
108
def _context_default (self ):
@@ -113,7 +154,6 @@ def start_kernel(self, kernel_name=None, **kwargs):
113
154
self ._kernels [kernel_id ] = km
114
155
return kernel_id
115
156
116
- @kernel_method
117
157
def shutdown_kernel (self , kernel_id , now = False , restart = False ):
118
158
"""Shutdown a kernel by its kernel uuid.
119
159
@@ -127,8 +167,21 @@ def shutdown_kernel(self, kernel_id, now=False, restart=False):
127
167
Will the kernel be restarted?
128
168
"""
129
169
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 )
130
179
self .remove_kernel (kernel_id )
131
180
181
+ if not restart and kernel .transport == 'tcp' :
182
+ for port in ports :
183
+ self .currently_used_ports .remove (port )
184
+
132
185
@kernel_method
133
186
def request_shutdown (self , kernel_id , restart = False ):
134
187
"""Ask a kernel to shut down by its kernel uuid"""
0 commit comments