|
12 | 12 | import runpy
|
13 | 13 | import requests
|
14 | 14 |
|
15 |
| -from dash.testing.errors import NoAppFoundError, TestingTimeoutError, ServerCloseError |
| 15 | +from dash.testing.errors import ( |
| 16 | + NoAppFoundError, |
| 17 | + TestingTimeoutError, |
| 18 | + ServerCloseError, |
| 19 | + DashAppLoadingError, |
| 20 | +) |
16 | 21 | from dash.testing import wait
|
17 | 22 |
|
18 | 23 |
|
@@ -146,37 +151,51 @@ def _handle_error():
|
146 | 151 |
|
147 | 152 | app.server.errorhandler(500)(_handle_error)
|
148 | 153 |
|
| 154 | + if self.thread and self.thread.is_alive(): |
| 155 | + self.stop() |
| 156 | + |
149 | 157 | def run():
|
150 | 158 | app.scripts.config.serve_locally = True
|
151 | 159 | app.css.config.serve_locally = True
|
| 160 | + |
| 161 | + options = kwargs.copy() |
| 162 | + |
152 | 163 | if "port" not in kwargs:
|
153 |
| - kwargs["port"] = self.port = BaseDashRunner._next_port |
| 164 | + options["port"] = self.port = BaseDashRunner._next_port |
154 | 165 | BaseDashRunner._next_port += 1
|
155 | 166 | else:
|
156 |
| - self.port = kwargs["port"] |
| 167 | + self.port = options["port"] |
157 | 168 |
|
158 | 169 | try:
|
159 |
| - app.run_server(threaded=True, **kwargs) |
| 170 | + app.run_server(threaded=True, **options) |
160 | 171 | except SystemExit:
|
161 | 172 | logger.info("Server stopped")
|
162 | 173 |
|
163 |
| - self.thread = KillerThread(target=run) |
164 |
| - self.thread.daemon = True |
165 |
| - try: |
166 |
| - self.thread.start() |
167 |
| - except RuntimeError: # multiple call on same thread |
168 |
| - logger.exception("threaded server failed to start") |
169 |
| - self.started = False |
| 174 | + retries = 0 |
170 | 175 |
|
171 |
| - self.started = self.thread.is_alive() |
| 176 | + while not self.started and retries < 3: |
| 177 | + try: |
| 178 | + self.thread = KillerThread(target=run) |
| 179 | + self.thread.daemon = True |
| 180 | + self.thread.start() |
| 181 | + # wait until server is able to answer http request |
| 182 | + wait.until(lambda: self.accessible(self.url), timeout=2) |
| 183 | + self.started = self.thread.is_alive() |
| 184 | + except Exception as err: # pylint: disable=broad-except |
| 185 | + logger.exception(err) |
| 186 | + self.started = False |
| 187 | + retries += 1 |
| 188 | + BaseDashRunner._next_port += 1 |
172 | 189 |
|
173 |
| - # wait until server is able to answer http request |
174 |
| - wait.until(lambda: self.accessible(self.url), timeout=1) |
| 190 | + self.started = self.thread.is_alive() |
| 191 | + if not self.started: |
| 192 | + raise DashAppLoadingError("threaded server failed to start") |
175 | 193 |
|
176 | 194 | def stop(self):
|
177 | 195 | self.thread.kill()
|
178 | 196 | self.thread.join()
|
179 | 197 | wait.until_not(self.thread.is_alive, self.stop_timeout)
|
| 198 | + self.started = False |
180 | 199 |
|
181 | 200 |
|
182 | 201 | class ProcessRunner(BaseDashRunner):
|
|
0 commit comments