7
7
import subprocess
8
8
import logging
9
9
import inspect
10
+ import ctypes
10
11
11
12
import runpy
12
- import flask
13
13
import requests
14
14
15
15
from dash .testing .errors import NoAppFoundError , TestingTimeoutError , ServerCloseError
@@ -102,6 +102,26 @@ def tmp_app_path(self):
102
102
return self ._tmp_app_path
103
103
104
104
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
+
105
125
class ThreadedRunner (BaseDashRunner ):
106
126
"""Runs a dash application in a thread.
107
127
@@ -110,25 +130,14 @@ class ThreadedRunner(BaseDashRunner):
110
130
111
131
def __init__ (self , keep_open = False , stop_timeout = 3 ):
112
132
super ().__init__ (keep_open = keep_open , stop_timeout = stop_timeout )
113
- self .stop_route = "/_stop-{}" .format (uuid .uuid4 ().hex )
114
133
self .thread = None
115
134
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
-
125
135
# pylint: disable=arguments-differ
126
136
def start (self , app , ** kwargs ):
127
137
"""Start the app server in threading flavor."""
128
- app .server .add_url_rule (self .stop_route , self .stop_route , self ._stop_server )
129
138
130
139
def _handle_error ():
131
- self ._stop_server ()
140
+ self .stop ()
132
141
133
142
app .server .errorhandler (500 )(_handle_error )
134
143
@@ -141,7 +150,7 @@ def run():
141
150
self .port = kwargs ["port" ]
142
151
app .run_server (threaded = True , ** kwargs )
143
152
144
- self .thread = threading . Thread (target = run )
153
+ self .thread = StoppableThread (target = run )
145
154
self .thread .daemon = True
146
155
try :
147
156
self .thread .start ()
@@ -155,7 +164,8 @@ def run():
155
164
wait .until (lambda : self .accessible (self .url ), timeout = 1 )
156
165
157
166
def stop (self ):
158
- requests .get ("{}{}" .format (self .url , self .stop_route ))
167
+ self .thread .kill ()
168
+ self .thread .join ()
159
169
wait .until_not (self .thread .is_alive , self .stop_timeout )
160
170
161
171
0 commit comments