1
1
from .vpython import GlowWidget , baseObj , vector , canvas , _browsertype
2
- from ._notebook_helpers import _in_spyder , _undo_vpython_import_in_spyder
2
+ from ._notebook_helpers import _in_spyder , _undo_vpython_import_in_spyder , _in_spyder_or_similar_IDE
3
3
4
4
from http .server import BaseHTTPRequestHandler , HTTPServer
5
5
import os
14
14
import copy
15
15
import socket
16
16
import multiprocessing
17
-
17
+ import time
18
18
19
19
import signal
20
20
from urllib .parse import unquote
21
21
22
22
from .rate_control import rate
23
23
24
+ makeDaemonic = (platform .system () == "Windows" )
24
25
25
26
# Redefine `Thread.run` to not show a traceback for Spyder when stopping
26
27
# the server by raising a KeyboardInterrupt or SystemExit.
27
- if _in_spyder :
28
+ if _in_spyder_or_similar_IDE :
28
29
def install_thread_stopped_message ():
29
30
"""
30
31
Workaround to prevent showing a traceback when VPython server stops.
@@ -38,7 +39,8 @@ def run(*args, **kwargs):
38
39
try :
39
40
run_old (* args , ** kwargs )
40
41
except (KeyboardInterrupt , SystemExit ):
41
- print ("VPython server stopped." )
42
+ pass
43
+ # ("VPython server stopped.")
42
44
except :
43
45
raise
44
46
threading .Thread .run = run
@@ -49,23 +51,32 @@ def run(*args, **kwargs):
49
51
50
52
# Check for Ctrl+C. SIGINT will also be sent by our code if WServer is closed.
51
53
def signal_handler (signal , frame ):
54
+ #print("in signal handler, calling stop server")
52
55
stop_server ()
53
56
54
-
55
57
signal .signal (signal .SIGINT , signal_handler )
56
58
57
59
# Requests from client to http server can be the following:
58
60
# get glowcomm.html, library .js files, images, or font files
59
61
60
62
61
- def find_free_port (port ):
63
+ def find_free_port (port = 0 ):
62
64
s = socket .socket ()
63
65
s .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
64
- s .bind (('' , port ))
66
+ #if hasattr(socket, 'SO_REUSEPORT'): # This may be required on systems that support it. Needs testing.
67
+ # s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
68
+ try :
69
+ s .bind (('' , port )) # bind to a port
70
+ except :
71
+ raise
65
72
return s .getsockname ()[1 ]
66
73
67
- __HTTP_PORT = find_free_port (4200 )
68
- __SOCKET_PORT = find_free_port (4201 )
74
+ if "VPYTHON_HTTP_PORT" in os .environ :
75
+ __HTTP_PORT = int (os .environ ["VPYTHON_HTTP_PORT" ])
76
+ else :
77
+ __HTTP_PORT = find_free_port ()
78
+
79
+ __SOCKET_PORT = find_free_port ()
69
80
70
81
try :
71
82
if platform .python_implementation () == 'PyPy' :
@@ -74,17 +85,17 @@ def find_free_port(port):
74
85
except :
75
86
pass
76
87
77
- # try: # machinery for reusing ports (2023/12/09 always use 4200 and 4201)
78
- # fd = open('free_ports')
79
- # __HTTP_PORT = int(fd.readline())
80
- # __SOCKET_PORT = int(fd.readline())
88
+ # try: # machinery for reusing ports
89
+ # fd = open('free_ports')
90
+ # __HTTP_PORT = int(fd.readline())
91
+ # __SOCKET_PORT = int(fd.readline())
81
92
# except:
82
- # __HTTP_PORT = find_free_port()
83
- # __SOCKET_PORT = find_free_port()
84
- # fd = open('free_ports', 'w') # this writes to user program's directory
85
- # fd.write(str(__HTTP_PORT))
86
- # fd.write('\n')
87
- # fd.write(str(__SOCKET_PORT))
93
+ # __HTTP_PORT = find_free_port()
94
+ # __SOCKET_PORT = find_free_port()
95
+ # fd = open('free_ports', 'w') # this writes to user program's directory
96
+ # fd.write(str(__HTTP_PORT))
97
+ # fd.write('\n')
98
+ # fd.write(str(__SOCKET_PORT))
88
99
89
100
# Make it possible for glowcomm.html to find out what the websocket port is:
90
101
js = __file__ .replace (
@@ -211,8 +222,18 @@ async def onMessage(self, data, isBinary):
211
222
# message format used by notebook
212
223
msg = {'content' : {'data' : [m ]}}
213
224
loop = asyncio .get_event_loop ()
214
- await loop .run_in_executor (None , GW .handle_msg , msg )
215
-
225
+ try :
226
+ await loop .run_in_executor (None , GW .handle_msg , msg )
227
+ except :
228
+ #
229
+ # this will throw a runtime exception after the main Thread
230
+ # has stopped, but we don't really case since the main thread
231
+ # is no longer there to do anything anyway.
232
+ if threading .main_thread ().is_alive ():
233
+ raise
234
+ else :
235
+ pass
236
+
216
237
def onClose (self , wasClean , code , reason ):
217
238
"""Called when browser tab is closed."""
218
239
global websocketserving
@@ -229,14 +250,14 @@ def onClose(self, wasClean, code, reason):
229
250
# need it here because in spyder the script may have stopped on its
230
251
# own ( because it has no infinite loop in it ) so the only signal
231
252
# that the tab has been closed comes via the websocket.
232
- if _in_spyder :
253
+ if _in_spyder_or_similar_IDE :
233
254
_undo_vpython_import_in_spyder ()
234
255
235
256
# We want to exit, but the main thread is running.
236
257
# Only the main thread can properly call sys.exit, so have a signal
237
258
# handler call it on the main thread's behalf.
238
259
if platform .system () == 'Windows' :
239
- if threading .main_thread ().is_alive () and not _in_spyder :
260
+ if threading .main_thread ().is_alive () and not _in_spyder_or_similar_IDE :
240
261
# On windows, if we get here then this signal won't be caught
241
262
# by our signal handler. Just call it ourselves.
242
263
os .kill (os .getpid (), signal .CTRL_C_EVENT )
@@ -247,19 +268,24 @@ def onClose(self, wasClean, code, reason):
247
268
248
269
249
270
try :
271
+ no_launch = os .environ .get ("VPYTHON_NO_LAUNCH_BROWSER" , False )
272
+ if no_launch == "0" :
273
+ no_launch = False
250
274
if platform .python_implementation () == 'PyPy' :
251
275
server_address = ('' , 0 ) # let HTTPServer choose a free port
252
276
__server = HTTPServer (server_address , serveHTTP )
253
277
port = __server .server_port # get the chosen port
254
278
# Change the global variable to store the actual port used
255
279
__HTTP_PORT = port
256
- _webbrowser .open ('http://localhost:{}' .format (port )
280
+ if not no_launch :
281
+ _webbrowser .open ('http://localhost:{}' .format (port )
257
282
) # or webbrowser.open_new_tab()
258
283
else :
259
284
__server = HTTPServer (('' , __HTTP_PORT ), serveHTTP )
260
285
# or webbrowser.open_new_tab()
261
- if _browsertype == 'default' : # uses default browser
262
- _webbrowser .open ('http://localhost:{}' .format (__HTTP_PORT ))
286
+ if not no_launch :
287
+ if _browsertype == 'default' : # uses default browser
288
+ _webbrowser .open ('http://localhost:{}' .format (__HTTP_PORT ))
263
289
264
290
except :
265
291
pass
@@ -293,7 +319,7 @@ def start_Qapp(port):
293
319
__m = multiprocessing .Process (target = start_Qapp , args = (__HTTP_PORT ,))
294
320
__m .start ()
295
321
296
- __w = threading .Thread (target = __server .serve_forever )
322
+ __w = threading .Thread (target = __server .serve_forever , daemon = makeDaemonic )
297
323
__w .start ()
298
324
299
325
@@ -326,14 +352,16 @@ def start_websocket_server():
326
352
# Put the websocket server in a separate thread running its own event loop.
327
353
# That works even if some other program (e.g. spyder) already running an
328
354
# async event loop.
329
- __t = threading .Thread (target = start_websocket_server )
355
+ __t = threading .Thread (target = start_websocket_server , daemon = makeDaemonic )
330
356
__t .start ()
331
357
332
358
333
359
def stop_server ():
334
360
"""Shuts down all threads and exits cleanly."""
361
+ #print("in stop server")
335
362
global __server
336
363
__server .shutdown ()
364
+
337
365
event_loop = txaio .config .loop
338
366
event_loop .stop ()
339
367
# We've told the event loop to stop, but it won't shut down until we poke
@@ -343,28 +371,45 @@ def stop_server():
343
371
# If we are in spyder, undo our import. This gets done in the websocket
344
372
# server onClose above if the browser tab is closed but is not done
345
373
# if the user stops the kernel instead.
346
- if _in_spyder :
374
+ if _in_spyder_or_similar_IDE :
347
375
_undo_vpython_import_in_spyder ()
348
376
349
377
# We don't want Ctrl-C to try to sys.exit inside spyder, i.e.
350
378
# in an ipython console with a separate python kernel running.
351
- if _in_spyder :
379
+ if _in_spyder_or_similar_IDE :
352
380
raise KeyboardInterrupt
353
381
354
382
if threading .main_thread ().is_alive ():
383
+ #print("main is alive...")
355
384
sys .exit (0 )
356
385
else :
357
- pass
386
+ #
387
+ # check to see if the event loop is still going, if so join it.
388
+ #
389
+ #print("main is dead..")
390
+ if __t .is_alive ():
391
+ #print("__t is alive still")
392
+ if threading .get_ident () != __t .ident :
393
+ #print("but it's not my thread, so I'll join...")
394
+ __t .join ()
395
+ else :
396
+ #print("__t is alive, but that's my thread! So skip it.")
397
+ pass
398
+ else :
399
+ if makeDaemonic :
400
+ sys .exit (0 )
401
+
358
402
# If the main thread has already stopped, the python interpreter
359
403
# is likely just running .join on the two remaining threads (in
360
404
# python/threading.py:_shutdown). Since we just stopped those threads,
361
405
# we'll now exit.
362
-
363
-
406
+
364
407
GW = GlowWidget ()
365
408
366
409
while not (httpserving and websocketserving ): # try to make sure setup is complete
367
- rate (60 )
410
+ time .sleep (0.1 )
411
+
368
412
369
413
# Dummy variable to import
370
414
_ = None
415
+
0 commit comments