Skip to content

Commit 4e0195b

Browse files
committed
Protect shutdown with lock. Allow shutdown more than once.
1 parent 0e369e4 commit 4e0195b

2 files changed

Lines changed: 43 additions & 31 deletions

File tree

src/qasync/__init__.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import time
2323
from concurrent.futures import Future
2424
from queue import Queue
25+
from threading import Lock
2526
from typing import TYPE_CHECKING, Literal, Tuple, cast, get_args
2627

2728
logger = logging.getLogger(__name__)
@@ -172,45 +173,42 @@ def __init__(self, max_workers=10, stack_size=None):
172173
self.__workers = [
173174
_QThreadWorker(self.__queue, i + 1, stack_size) for i in range(max_workers)
174175
]
176+
self.__shutdown_lock = Lock()
175177
self.__been_shutdown = False
176178

177179
for w in self.__workers:
178180
w.start()
179181

180182
def submit(self, callback, *args, **kwargs):
181-
if self.__been_shutdown:
182-
raise RuntimeError("QThreadExecutor has been shutdown")
183+
with self.__shutdown_lock:
184+
if self.__been_shutdown:
185+
raise RuntimeError("QThreadExecutor has been shutdown")
183186

184-
future = Future()
185-
self._logger.debug(
186-
"Submitting callback %s with args %s and kwargs %s to thread worker queue",
187-
callback,
188-
args,
189-
kwargs,
190-
)
191-
self.__queue.put((future, callback, args, kwargs))
192-
return future
187+
future = Future()
188+
self._logger.debug(
189+
"Submitting callback %s with args %s and kwargs %s to thread worker queue",
190+
callback,
191+
args,
192+
kwargs,
193+
)
194+
self.__queue.put((future, callback, args, kwargs))
195+
return future
193196

194197
def map(self, func, *iterables, timeout=None):
195198
raise NotImplementedError("use as_completed on the event loop")
196199

197200
def shutdown(self, wait=True):
198-
if self.__been_shutdown:
199-
raise RuntimeError("QThreadExecutor has been shutdown")
200-
201-
self.__been_shutdown = True
202-
203-
self._logger.debug("Shutting down")
204-
for i in range(len(self.__workers)):
205-
# Signal workers to stop
206-
self.__queue.put(None)
207-
if wait:
208-
for w in self.__workers:
209-
w.wait()
201+
with self.__shutdown_lock:
202+
self.__been_shutdown = True
203+
self._logger.debug("Shutting down")
204+
for i in range(len(self.__workers)):
205+
# Signal workers to stop
206+
self.__queue.put(None)
207+
if wait:
208+
for w in self.__workers:
209+
w.wait()
210210

211211
def __enter__(self, *args):
212-
if self.__been_shutdown:
213-
raise RuntimeError("QThreadExecutor has been shutdown")
214212
return self
215213

216214
def __exit__(self, *args):

tests/test_qthreadexec.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,16 @@ def shutdown_executor():
4444
return exe
4545

4646

47-
def test_shutdown_after_shutdown(shutdown_executor):
48-
with pytest.raises(RuntimeError):
49-
shutdown_executor.shutdown()
47+
@pytest.mark.parametrize("wait", [True, False])
48+
def test_shutdown_after_shutdown(shutdown_executor, wait):
49+
# it is safe to shutdown twice
50+
shutdown_executor.shutdown(wait=wait)
5051

5152

5253
def test_ctx_after_shutdown(shutdown_executor):
53-
with pytest.raises(RuntimeError):
54-
with shutdown_executor:
55-
pass
54+
# it is safe to enter and exit the context after shutdown
55+
with shutdown_executor:
56+
pass
5657

5758

5859
def test_submit_after_shutdown(shutdown_executor):
@@ -104,3 +105,16 @@ def test_no_stale_reference_as_result(executor, disable_executor_logging):
104105
assert collected is True, (
105106
"Stale reference to executor result not collected within timeout."
106107
)
108+
109+
110+
def test_context(executor):
111+
"""Test that the context manager will shutdown executor"""
112+
with executor:
113+
f = executor.submit(lambda: 42)
114+
assert f.result() == 42
115+
116+
# it can be entered again
117+
with executor:
118+
# but will fail when we submit
119+
with pytest.raises(RuntimeError):
120+
executor.submit(lambda: 42)

0 commit comments

Comments
 (0)