Skip to content

Commit 3f03bd7

Browse files
committed
Remove daemon threads in concurrent.futures
1 parent 37fcbb6 commit 3f03bd7

File tree

3 files changed

+38
-34
lines changed

3 files changed

+38
-34
lines changed

Lib/concurrent/futures/process.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,6 @@
5959
import sys
6060
import traceback
6161

62-
# Workers are created as daemon threads and processes. This is done to allow the
63-
# interpreter to exit when there are still idle processes in a
64-
# ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However,
65-
# allowing workers to die with the interpreter has two undesirable properties:
66-
# - The workers would still be running during interpreter shutdown,
67-
# meaning that they would fail in unpredictable ways.
68-
# - The workers could be killed while evaluating a work item, which could
69-
# be bad if the callable being evaluated has external side-effects e.g.
70-
# writing to a file.
71-
#
72-
# To work around this problem, an exit handler is installed which tells the
73-
# workers to exit when their work queues are empty and then waits until the
74-
# threads/processes finish.
75-
7662
_threads_wakeups = weakref.WeakKeyDictionary()
7763
_global_shutdown = False
7864

@@ -107,6 +93,12 @@ def _python_exit():
10793
for t, _ in items:
10894
t.join()
10995

96+
# Register for `_python_exit()` to be called just before joining all
97+
# non-daemon threads. This is used instead of `atexit.register()` for
98+
# compatibility with subinterpreters, which no longer support daemon threads.
99+
# See bpo-39812 for context.
100+
threading._register_atexit(_python_exit)
101+
110102
# Controls how many more calls than processes will be queued in the call queue.
111103
# A smaller number will mean that processes spend more time idle waiting for
112104
# work while a larger number will make Future.cancel() succeed less frequently
@@ -306,9 +298,7 @@ def weakref_cb(_, thread_wakeup=self.thread_wakeup):
306298
# {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
307299
self.pending_work_items = executor._pending_work_items
308300

309-
# Set this thread to be daemonized
310301
super().__init__()
311-
self.daemon = True
312302

313303
def run(self):
314304
# Main loop for the executor manager thread.
@@ -732,5 +722,3 @@ def shutdown(self, wait=True, *, cancel_futures=False):
732722
self._executor_manager_thread_wakeup = None
733723

734724
shutdown.__doc__ = _base.Executor.shutdown.__doc__
735-
736-
atexit.register(_python_exit)

Lib/concurrent/futures/thread.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,6 @@
1313
import weakref
1414
import os
1515

16-
# Workers are created as daemon threads. This is done to allow the interpreter
17-
# to exit when there are still idle threads in a ThreadPoolExecutor's thread
18-
# pool (i.e. shutdown() was not called). However, allowing workers to die with
19-
# the interpreter has two undesirable properties:
20-
# - The workers would still be running during interpreter shutdown,
21-
# meaning that they would fail in unpredictable ways.
22-
# - The workers could be killed while evaluating a work item, which could
23-
# be bad if the callable being evaluated has external side-effects e.g.
24-
# writing to a file.
25-
#
26-
# To work around this problem, an exit handler is installed which tells the
27-
# workers to exit when their work queues are empty and then waits until the
28-
# threads finish.
29-
3016
_threads_queues = weakref.WeakKeyDictionary()
3117
_shutdown = False
3218
# Lock that ensures that new workers are not created while the interpreter is
@@ -43,7 +29,11 @@ def _python_exit():
4329
for t, q in items:
4430
t.join()
4531

46-
atexit.register(_python_exit)
32+
# Register for `_python_exit()` to be called just before joining all
33+
# non-daemon threads. This is used instead of `atexit.register()` for
34+
# compatibility with subinterpreters, which no longer support daemon threads.
35+
# See bpo-39812 for context.
36+
threading._register_atexit(_python_exit)
4737

4838

4939
class _WorkItem(object):
@@ -197,7 +187,6 @@ def weakref_cb(_, q=self._work_queue):
197187
self._work_queue,
198188
self._initializer,
199189
self._initargs))
200-
t.daemon = True
201190
t.start()
202191
self._threads.add(t)
203192
_threads_queues[t] = self._work_queue

Lib/threading.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os as _os
44
import sys as _sys
55
import _thread
6+
import functools
67

78
from time import monotonic as _time
89
from _weakrefset import WeakSet
@@ -1346,6 +1347,26 @@ def enumerate():
13461347
with _active_limbo_lock:
13471348
return list(_active.values()) + list(_limbo.values())
13481349

1350+
1351+
_threading_atexits = None
1352+
1353+
def _register_atexit(func, *arg, **kwargs):
1354+
"""CPython internal: register *func* to be called before joining threads.
1355+
1356+
The registered *func* is called with its arguments just before all
1357+
non-daemon threads are joined in `_shutdown()`. It provides a similar
1358+
purpose to `atexit.register()`, but its functions are called prior to
1359+
threading shutdown instead of interpreter shutdown.
1360+
1361+
For similarity to atexit, the registed functions are called in reverse.
1362+
"""
1363+
global _threading_atexits
1364+
if _threading_atexits is None:
1365+
_threading_atexits = []
1366+
call = functools.partial(func, *arg, **kwargs)
1367+
_threading_atexits.append(call)
1368+
1369+
13491370
from _thread import stack_size
13501371

13511372
# Create the main thread object,
@@ -1376,6 +1397,12 @@ def _shutdown():
13761397
tlock.release()
13771398
_main_thread._stop()
13781399

1400+
# Call registered threading atexit functions before threads are joined
1401+
if _threading_atexits is not None:
1402+
# Order is reversed, similar to atexit.
1403+
for atexit_call in reversed(_threading_atexits):
1404+
atexit_call()
1405+
13791406
# Join all non-deamon threads
13801407
while True:
13811408
with _shutdown_locks_lock:

0 commit comments

Comments
 (0)