8
8
import asyncio
9
9
import json
10
10
import os
11
+ import signal
11
12
import subprocess
12
13
import sys
13
14
import time
@@ -38,10 +39,11 @@ class LocalBox(BaseBox):
38
39
"""
39
40
40
41
_instance : Optional ["LocalBox" ] = None
42
+ _jupyter_pids : List [int ] = []
41
43
42
44
def __new__ (cls , * args , ** kwargs ):
43
45
if not cls ._instance :
44
- cls ._instance = super ().__new__ (cls , * args , ** kwargs )
46
+ cls ._instance = super ().__new__ (cls )
45
47
else :
46
48
if settings .SHOW_INFO :
47
49
print (
@@ -52,15 +54,16 @@ def __new__(cls, *args, **kwargs):
52
54
)
53
55
return cls ._instance
54
56
55
- def __init__ (self ) -> None :
56
- super ().__init__ ()
57
+ def __init__ (self , / , ** kwargs ) -> None :
58
+ super ().__init__ (session_id = kwargs . pop ( "session_id" , None ) )
57
59
self .port : int = 8888
58
60
self .kernel_id : Optional [dict ] = None
59
61
self .ws : Union [WebSocketClientProtocol , ClientConnection , None ] = None
60
62
self .jupyter : Union [Process , subprocess .Popen , None ] = None
61
63
self .aiohttp_session : Optional [aiohttp .ClientSession ] = None
62
64
63
65
def start (self ) -> CodeBoxStatus :
66
+ self .session_id = uuid4 ()
64
67
os .makedirs (".codebox" , exist_ok = True )
65
68
self ._check_port ()
66
69
if settings .VERBOSE :
@@ -84,6 +87,7 @@ def start(self) -> CodeBoxStatus:
84
87
stderr = out ,
85
88
cwd = ".codebox" ,
86
89
)
90
+ self ._jupyter_pids .append (self .jupyter .pid )
87
91
except FileNotFoundError :
88
92
raise ModuleNotFoundError (
89
93
"Jupyter Kernel Gateway not found, please install it with:\n "
@@ -100,7 +104,10 @@ def start(self) -> CodeBoxStatus:
100
104
if settings .VERBOSE :
101
105
print ("Waiting for kernel to start..." )
102
106
time .sleep (1 )
107
+ self ._connect ()
108
+ return CodeBoxStatus (status = "started" )
103
109
110
+ def _connect (self ) -> None :
104
111
response = requests .post (
105
112
f"{ self .kernel_url } /kernels" ,
106
113
headers = {"Content-Type" : "application/json" },
@@ -112,20 +119,15 @@ def start(self) -> CodeBoxStatus:
112
119
113
120
self .ws = ws_connect_sync (f"{ self .ws_url } /kernels/{ self .kernel_id } /channels" )
114
121
115
- return CodeBoxStatus (status = "started" )
116
-
117
122
def _check_port (self ) -> None :
118
123
try :
119
124
response = requests .get (f"http://localhost:{ self .port } " , timeout = 90 )
120
125
except requests .exceptions .ConnectionError :
121
126
pass
122
127
else :
123
- try :
124
- requests .post (f"http://localhost:{ self .port } /api/shutdown" )
125
- except requests .exceptions .ConnectionError :
126
- if response .status_code == 200 :
127
- self .port += 1
128
- self ._check_port ()
128
+ if response .status_code == 200 :
129
+ self .port += 1
130
+ self ._check_port ()
129
131
130
132
def _check_installed (self ) -> None :
131
133
try :
@@ -139,6 +141,7 @@ def _check_installed(self) -> None:
139
141
raise
140
142
141
143
async def astart (self ) -> CodeBoxStatus :
144
+ self .session_id = uuid4 ()
142
145
os .makedirs (".codebox" , exist_ok = True )
143
146
self .aiohttp_session = aiohttp .ClientSession ()
144
147
await self ._acheck_port ()
@@ -161,6 +164,7 @@ async def astart(self) -> CodeBoxStatus:
161
164
stderr = out ,
162
165
cwd = ".codebox" ,
163
166
)
167
+ self ._jupyter_pids .append (self .jupyter .pid )
164
168
except Exception as e :
165
169
print (e )
166
170
raise ModuleNotFoundError (
@@ -180,7 +184,12 @@ async def astart(self) -> CodeBoxStatus:
180
184
if settings .VERBOSE :
181
185
print ("Waiting for kernel to start..." )
182
186
await asyncio .sleep (1 )
187
+ await self ._aconnect ()
188
+ return CodeBoxStatus (status = "started" )
183
189
190
+ async def _aconnect (self ) -> None :
191
+ if self .aiohttp_session is None :
192
+ self .aiohttp_session = aiohttp .ClientSession ()
184
193
response = await self .aiohttp_session .post (
185
194
f"{ self .kernel_url } /kernels" , headers = {"Content-Type" : "application/json" }
186
195
)
@@ -189,8 +198,6 @@ async def astart(self) -> CodeBoxStatus:
189
198
raise Exception ("Could not start kernel" )
190
199
self .ws = await ws_connect (f"{ self .ws_url } /kernels/{ self .kernel_id } /channels" )
191
200
192
- return CodeBoxStatus (status = "started" )
193
-
194
201
async def _acheck_port (self ) -> None :
195
202
try :
196
203
if self .aiohttp_session is None :
@@ -206,6 +213,9 @@ async def _acheck_port(self) -> None:
206
213
await self ._acheck_port ()
207
214
208
215
def status (self ) -> CodeBoxStatus :
216
+ if not self .kernel_id :
217
+ self ._connect ()
218
+
209
219
return CodeBoxStatus (
210
220
status = "running"
211
221
if self .kernel_id
@@ -214,6 +224,8 @@ def status(self) -> CodeBoxStatus:
214
224
)
215
225
216
226
async def astatus (self ) -> CodeBoxStatus :
227
+ if not self .kernel_id :
228
+ await self ._aconnect ()
217
229
return CodeBoxStatus (
218
230
status = "running"
219
231
if self .kernel_id
@@ -242,9 +254,9 @@ def run(
242
254
if retry <= 0 :
243
255
raise RuntimeError ("Could not connect to kernel" )
244
256
if not self .ws :
245
- self .start ()
257
+ self ._connect ()
246
258
if not self .ws :
247
- raise RuntimeError ("Could not connect to kernel " )
259
+ raise RuntimeError ("Jupyter not running. Make sure to start it first. " )
248
260
249
261
if settings .VERBOSE :
250
262
print ("Running code:\n " , code )
@@ -354,9 +366,9 @@ async def arun(
354
366
if retry <= 0 :
355
367
raise RuntimeError ("Could not connect to kernel" )
356
368
if not self .ws :
357
- await self .astart ()
369
+ await self ._aconnect ()
358
370
if not self .ws :
359
- raise RuntimeError ("Could not connect to kernel " )
371
+ raise RuntimeError ("Jupyter not running. Make sure to start it first. " )
360
372
361
373
if settings .VERBOSE :
362
374
print ("Running code:\n " , code )
@@ -488,20 +500,16 @@ async def alist_files(self) -> List[CodeBoxFile]:
488
500
return await asyncio .to_thread (self .list_files )
489
501
490
502
def restart (self ) -> CodeBoxStatus :
491
- if self .jupyter is not None :
492
- self .stop ()
493
- else :
494
- self .start ()
495
503
return CodeBoxStatus (status = "restarted" )
496
504
497
505
async def arestart (self ) -> CodeBoxStatus :
498
- if self .jupyter is not None :
499
- await self .astop ()
500
- else :
501
- await self .astart ()
502
506
return CodeBoxStatus (status = "restarted" )
503
507
504
508
def stop (self ) -> CodeBoxStatus :
509
+ for pid in self ._jupyter_pids :
510
+ print (f"Killing { pid } " )
511
+ os .kill (pid , signal .SIGTERM )
512
+
505
513
if self .jupyter is not None :
506
514
self .jupyter .terminate ()
507
515
self .jupyter .wait ()
0 commit comments