Skip to content
This repository was archived by the owner on Jun 3, 2024. It is now read-only.

Commit 3cd2da4

Browse files
authored
Merge pull request #80 from T4rk1n/63-thread-kill
Fix shutdown deprecation
2 parents 8978912 + 98894ec commit 3cd2da4

File tree

3 files changed

+37
-26
lines changed

3 files changed

+37
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
44

55
## Unreleased
66
### Fixed
7-
- Fixed `werkzeug` 2.1.0 import and `skip` calculation
7+
- Fixed `werkzeug` 2.1.0 import and `skip` calculation, shutdown deprecation warning.
88

99
## 0.4.1 - 2022-02-16
1010
### Fixed

jupyter_dash/_stoppable_thread.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import ctypes
2+
import threading
3+
4+
5+
class StoppableThread(threading.Thread):
6+
def get_id(self):
7+
if hasattr(self, "_thread_id"):
8+
return self._thread_id
9+
for thread_id, thread in threading._active.items():
10+
if thread is self:
11+
return thread_id
12+
13+
def kill(self):
14+
thread_id = self.get_id()
15+
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
16+
ctypes.c_long(thread_id), ctypes.py_object(SystemExit)
17+
)
18+
if res == 0:
19+
raise ValueError(f"Invalid thread id: {thread_id}")
20+
if res > 1:
21+
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), None)
22+
raise SystemExit("Stopping thread failure")

jupyter_dash/jupyter_app.py

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import dash
22
import os
33
import requests
4-
from flask import request
54
import flask.cli
6-
from threading import Thread
75
from retrying import retry
86
import io
97
import re
@@ -19,12 +17,13 @@
1917
import uuid
2018

2119
from .comms import _dash_comm, _jupyter_config, _request_jupyter_config
20+
from ._stoppable_thread import StoppableThread
2221

2322

2423
def _get_skip(error: Exception):
2524
tb = traceback.format_exception(type(error), error, error.__traceback__)
2625
skip = 0
27-
for i, line in enumerate(text):
26+
for i, line in enumerate(tb):
2827
if "%% callback invoked %%" in line:
2928
skip = i + 1
3029
break
@@ -49,6 +48,8 @@ class JupyterDash(dash.Dash):
4948
_in_colab = "google.colab" in sys.modules
5049
_token = str(uuid.uuid4())
5150

51+
_server_threads = {}
52+
5253
@classmethod
5354
def infer_jupyter_proxy_config(cls):
5455
"""
@@ -138,15 +139,6 @@ def __init__(self, name=None, server_url=None, **kwargs):
138139

139140
self.server_url = server_url
140141

141-
# Register route to shut down server
142-
@self.server.route('/_shutdown_' + JupyterDash._token, methods=['GET'])
143-
def shutdown():
144-
func = request.environ.get('werkzeug.server.shutdown')
145-
if func is None:
146-
raise RuntimeError('Not running with the Werkzeug Server')
147-
func()
148-
return 'Server shutting down...'
149-
150142
# Register route that we can use to poll to see when server is running
151143
@self.server.route('/_alive_' + JupyterDash._token, methods=['GET'])
152144
def alive():
@@ -225,7 +217,9 @@ def run_server(
225217
inline_exceptions = mode == "inline"
226218

227219
# Terminate any existing server using this port
228-
self._terminate_server_for_port(host, port)
220+
old_server = self._server_threads.get((host, port))
221+
if old_server:
222+
old_server.kill()
229223

230224
# Configure pathname prefix
231225
requests_pathname_prefix = self.config.get('requests_pathname_prefix', None)
@@ -297,12 +291,17 @@ def run_server(
297291
wait_exponential_max=1000
298292
)
299293
def run():
300-
super_run_server(**kwargs)
294+
try:
295+
super_run_server(**kwargs)
296+
except SystemExit:
297+
pass
301298

302-
thread = Thread(target=run)
299+
thread = StoppableThread(target=run)
303300
thread.setDaemon(True)
304301
thread.start()
305302

303+
self._server_threads[(host, port)] = thread
304+
306305
# Wait for server to start up
307306
alive_url = "http://{host}:{port}/_alive_{token}".format(
308307
host=host, port=port, token=JupyterDash._token
@@ -414,16 +413,6 @@ def _wrap_errors(error):
414413

415414
return html_str, 500
416415

417-
@classmethod
418-
def _terminate_server_for_port(cls, host, port):
419-
shutdown_url = "http://{host}:{port}/_shutdown_{token}".format(
420-
host=host, port=port, token=JupyterDash._token
421-
)
422-
try:
423-
response = requests.get(shutdown_url)
424-
except Exception as e:
425-
pass
426-
427416

428417
def _custom_formatargvalues(
429418
args, varargs, varkw, locals,

0 commit comments

Comments
 (0)