11from .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
33
44from http .server import BaseHTTPRequestHandler , HTTPServer
55import os
1414import copy
1515import socket
1616import multiprocessing
17-
17+ import time
1818
1919import signal
2020from urllib .parse import unquote
2121
2222from .rate_control import rate
2323
24+ makeDaemonic = (platform .system () == "Windows" )
2425
2526# Redefine `Thread.run` to not show a traceback for Spyder when stopping
2627# the server by raising a KeyboardInterrupt or SystemExit.
27- if _in_spyder :
28+ if _in_spyder_or_similar_IDE :
2829 def install_thread_stopped_message ():
2930 """
3031 Workaround to prevent showing a traceback when VPython server stops.
@@ -38,7 +39,8 @@ def run(*args, **kwargs):
3839 try :
3940 run_old (* args , ** kwargs )
4041 except (KeyboardInterrupt , SystemExit ):
41- print ("VPython server stopped." )
42+ pass
43+ # ("VPython server stopped.")
4244 except :
4345 raise
4446 threading .Thread .run = run
@@ -49,23 +51,32 @@ def run(*args, **kwargs):
4951
5052# Check for Ctrl+C. SIGINT will also be sent by our code if WServer is closed.
5153def signal_handler (signal , frame ):
54+ #print("in signal handler, calling stop server")
5255 stop_server ()
5356
54-
5557signal .signal (signal .SIGINT , signal_handler )
5658
5759# Requests from client to http server can be the following:
5860# get glowcomm.html, library .js files, images, or font files
5961
6062
61- def find_free_port (port ):
63+ def find_free_port (port = 0 ):
6264 s = socket .socket ()
6365 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
6572 return s .getsockname ()[1 ]
6673
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 ()
6980
7081try :
7182 if platform .python_implementation () == 'PyPy' :
@@ -74,17 +85,17 @@ def find_free_port(port):
7485except :
7586 pass
7687
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())
8192# 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))
8899
89100# Make it possible for glowcomm.html to find out what the websocket port is:
90101js = __file__ .replace (
@@ -211,8 +222,18 @@ async def onMessage(self, data, isBinary):
211222 # message format used by notebook
212223 msg = {'content' : {'data' : [m ]}}
213224 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+
216237 def onClose (self , wasClean , code , reason ):
217238 """Called when browser tab is closed."""
218239 global websocketserving
@@ -229,14 +250,14 @@ def onClose(self, wasClean, code, reason):
229250 # need it here because in spyder the script may have stopped on its
230251 # own ( because it has no infinite loop in it ) so the only signal
231252 # that the tab has been closed comes via the websocket.
232- if _in_spyder :
253+ if _in_spyder_or_similar_IDE :
233254 _undo_vpython_import_in_spyder ()
234255
235256 # We want to exit, but the main thread is running.
236257 # Only the main thread can properly call sys.exit, so have a signal
237258 # handler call it on the main thread's behalf.
238259 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 :
240261 # On windows, if we get here then this signal won't be caught
241262 # by our signal handler. Just call it ourselves.
242263 os .kill (os .getpid (), signal .CTRL_C_EVENT )
@@ -247,19 +268,24 @@ def onClose(self, wasClean, code, reason):
247268
248269
249270try :
271+ no_launch = os .environ .get ("VPYTHON_NO_LAUNCH_BROWSER" , False )
272+ if no_launch == "0" :
273+ no_launch = False
250274 if platform .python_implementation () == 'PyPy' :
251275 server_address = ('' , 0 ) # let HTTPServer choose a free port
252276 __server = HTTPServer (server_address , serveHTTP )
253277 port = __server .server_port # get the chosen port
254278 # Change the global variable to store the actual port used
255279 __HTTP_PORT = port
256- _webbrowser .open ('http://localhost:{}' .format (port )
280+ if not no_launch :
281+ _webbrowser .open ('http://localhost:{}' .format (port )
257282 ) # or webbrowser.open_new_tab()
258283 else :
259284 __server = HTTPServer (('' , __HTTP_PORT ), serveHTTP )
260285 # 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 ))
263289
264290except :
265291 pass
@@ -293,7 +319,7 @@ def start_Qapp(port):
293319 __m = multiprocessing .Process (target = start_Qapp , args = (__HTTP_PORT ,))
294320 __m .start ()
295321
296- __w = threading .Thread (target = __server .serve_forever )
322+ __w = threading .Thread (target = __server .serve_forever , daemon = makeDaemonic )
297323__w .start ()
298324
299325
@@ -326,14 +352,16 @@ def start_websocket_server():
326352# Put the websocket server in a separate thread running its own event loop.
327353# That works even if some other program (e.g. spyder) already running an
328354# async event loop.
329- __t = threading .Thread (target = start_websocket_server )
355+ __t = threading .Thread (target = start_websocket_server , daemon = makeDaemonic )
330356__t .start ()
331357
332358
333359def stop_server ():
334360 """Shuts down all threads and exits cleanly."""
361+ #print("in stop server")
335362 global __server
336363 __server .shutdown ()
364+
337365 event_loop = txaio .config .loop
338366 event_loop .stop ()
339367 # We've told the event loop to stop, but it won't shut down until we poke
@@ -343,28 +371,45 @@ def stop_server():
343371 # If we are in spyder, undo our import. This gets done in the websocket
344372 # server onClose above if the browser tab is closed but is not done
345373 # if the user stops the kernel instead.
346- if _in_spyder :
374+ if _in_spyder_or_similar_IDE :
347375 _undo_vpython_import_in_spyder ()
348376
349377 # We don't want Ctrl-C to try to sys.exit inside spyder, i.e.
350378 # in an ipython console with a separate python kernel running.
351- if _in_spyder :
379+ if _in_spyder_or_similar_IDE :
352380 raise KeyboardInterrupt
353381
354382 if threading .main_thread ().is_alive ():
383+ #print("main is alive...")
355384 sys .exit (0 )
356385 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+
358402 # If the main thread has already stopped, the python interpreter
359403 # is likely just running .join on the two remaining threads (in
360404 # python/threading.py:_shutdown). Since we just stopped those threads,
361405 # we'll now exit.
362-
363-
406+
364407GW = GlowWidget ()
365408
366409while not (httpserving and websocketserving ): # try to make sure setup is complete
367- rate (60 )
410+ time .sleep (0.1 )
411+
368412
369413# Dummy variable to import
370414_ = None
415+
0 commit comments