Skip to content

Commit e19dc6d

Browse files
committed
Replace deprecated flask shutdown with thread kill. #1780
1 parent 6ee2328 commit e19dc6d

File tree

1 file changed

+25
-15
lines changed

1 file changed

+25
-15
lines changed

Diff for: dash/testing/application_runners.py

+25-15
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import subprocess
88
import logging
99
import inspect
10+
import ctypes
1011

1112
import runpy
12-
import flask
1313
import requests
1414

1515
from dash.testing.errors import NoAppFoundError, TestingTimeoutError, ServerCloseError
@@ -102,6 +102,26 @@ def tmp_app_path(self):
102102
return self._tmp_app_path
103103

104104

105+
class StoppableThread(threading.Thread):
106+
def get_id(self): # pylint: disable=R1710
107+
if hasattr(self, "_thread_id"):
108+
return self._thread_id
109+
for thread_id, thread in threading._active.items(): # pylint: disable=W0212
110+
if thread is self:
111+
return thread_id
112+
113+
def kill(self):
114+
thread_id = self.get_id()
115+
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
116+
ctypes.c_long(thread_id), ctypes.py_object(SystemExit)
117+
)
118+
if res == 0:
119+
raise ValueError(f"Invalid thread id: {thread_id}")
120+
if res > 1:
121+
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), None)
122+
raise SystemExit("Stopping thread failure")
123+
124+
105125
class ThreadedRunner(BaseDashRunner):
106126
"""Runs a dash application in a thread.
107127
@@ -110,25 +130,14 @@ class ThreadedRunner(BaseDashRunner):
110130

111131
def __init__(self, keep_open=False, stop_timeout=3):
112132
super().__init__(keep_open=keep_open, stop_timeout=stop_timeout)
113-
self.stop_route = "/_stop-{}".format(uuid.uuid4().hex)
114133
self.thread = None
115134

116-
@staticmethod
117-
def _stop_server():
118-
# https://werkzeug.palletsprojects.com/en/0.15.x/serving/#shutting-down-the-server
119-
stopper = flask.request.environ.get("werkzeug.server.shutdown")
120-
if stopper is None:
121-
raise RuntimeError("Not running with the Werkzeug Server")
122-
stopper()
123-
return "Flask server is shutting down"
124-
125135
# pylint: disable=arguments-differ
126136
def start(self, app, **kwargs):
127137
"""Start the app server in threading flavor."""
128-
app.server.add_url_rule(self.stop_route, self.stop_route, self._stop_server)
129138

130139
def _handle_error():
131-
self._stop_server()
140+
self.stop()
132141

133142
app.server.errorhandler(500)(_handle_error)
134143

@@ -141,7 +150,7 @@ def run():
141150
self.port = kwargs["port"]
142151
app.run_server(threaded=True, **kwargs)
143152

144-
self.thread = threading.Thread(target=run)
153+
self.thread = StoppableThread(target=run)
145154
self.thread.daemon = True
146155
try:
147156
self.thread.start()
@@ -155,7 +164,8 @@ def run():
155164
wait.until(lambda: self.accessible(self.url), timeout=1)
156165

157166
def stop(self):
158-
requests.get("{}{}".format(self.url, self.stop_route))
167+
self.thread.kill()
168+
self.thread.join()
159169
wait.until_not(self.thread.is_alive, self.stop_timeout)
160170

161171

0 commit comments

Comments
 (0)