22# Copyright (c) Jupyter Development Team.
33# Distributed under the terms of the Modified BSD License.
44import asyncio
5+ import json
56import os
67import socket
78import typing as t
89import uuid
910from functools import wraps
11+ from pathlib import Path
1012
1113import zmq
1214from traitlets import Any , Bool , Dict , DottedObjectName , Instance , Unicode , default , observe
1315from traitlets .config .configurable import LoggingConfigurable
1416from traitlets .utils .importstring import import_item
1517
18+ from .connect import KernelConnectionInfo
1619from .kernelspec import NATIVE_KERNEL_NAME , KernelSpecManager
1720from .manager import KernelManager
18- from .utils import ensure_async , run_sync
21+ from .utils import ensure_async , run_sync , utcnow
1922
2023
2124class DuplicateKernelError (Exception ):
@@ -105,9 +108,14 @@ def _context_default(self) -> zmq.Context:
105108 return zmq .Context ()
106109
107110 connection_dir = Unicode ("" )
111+ external_connection_dir = Unicode (None , allow_none = True )
108112
109113 _kernels = Dict ()
110114
115+ def __init__ (self , * args , ** kwargs ):
116+ super ().__init__ (* args , ** kwargs )
117+ self .kernel_id_to_connection_file = {}
118+
111119 def __del__ (self ):
112120 """Handle garbage collection. Destroy context if applicable."""
113121 if self ._created_context and self .context and not self .context .closed :
@@ -123,6 +131,51 @@ def __del__(self):
123131
124132 def list_kernel_ids (self ) -> t .List [str ]:
125133 """Return a list of the kernel ids of the active kernels."""
134+ if self .external_connection_dir is not None :
135+ external_connection_dir = Path (self .external_connection_dir )
136+ if external_connection_dir .is_dir ():
137+ connection_files = [p for p in external_connection_dir .iterdir () if p .is_file ()]
138+
139+ # remove kernels (whose connection file has disappeared) from our list
140+ k = list (self .kernel_id_to_connection_file .keys ())
141+ v = list (self .kernel_id_to_connection_file .values ())
142+ for connection_file in list (self .kernel_id_to_connection_file .values ()):
143+ if connection_file not in connection_files :
144+ kernel_id = k [v .index (connection_file )]
145+ del self .kernel_id_to_connection_file [kernel_id ]
146+ del self ._kernels [kernel_id ]
147+
148+ # add kernels (whose connection file appeared) to our list
149+ for connection_file in connection_files :
150+ if connection_file in self .kernel_id_to_connection_file .values ():
151+ continue
152+ try :
153+ connection_info : KernelConnectionInfo = json .loads (
154+ connection_file .read_text ()
155+ )
156+ except Exception : # noqa: S112
157+ continue
158+ self .log .debug ("Loading connection file %s" , connection_file )
159+ if not ("kernel_name" in connection_info and "key" in connection_info ):
160+ continue
161+ # it looks like a connection file
162+ kernel_id = self .new_kernel_id ()
163+ self .kernel_id_to_connection_file [kernel_id ] = connection_file
164+ km = self .kernel_manager_factory (
165+ parent = self ,
166+ log = self .log ,
167+ owns_kernel = False ,
168+ )
169+ km .load_connection_info (connection_info )
170+ km .last_activity = utcnow ()
171+ km .execution_state = "idle"
172+ km .connections = 1
173+ km .kernel_id = kernel_id
174+ km .kernel_name = connection_info ["kernel_name" ]
175+ km .ready .set_result (None )
176+
177+ self ._kernels [kernel_id ] = km
178+
126179 # Create a copy so we can iterate over kernels in operations
127180 # that delete keys.
128181 return list (self ._kernels .keys ())
0 commit comments