Added
Link Here
|
1 |
From: Victor Stinner <vstinner@python.org> |
2 |
Date: Mon, 9 Mar 2020 23:37:49 +0100 |
3 |
Subject: [PATCH] bpo-19466: Py_Finalize() clears daemon threads earlier |
4 |
(GH-18848) |
5 |
|
6 |
Clear the frames of daemon threads earlier during the Python shutdown to |
7 |
call objects destructors. So "unclosed file" resource warnings are now |
8 |
emitted for daemon threads in a more reliable way. |
9 |
|
10 |
Cleanup _PyThreadState_DeleteExcept() code: rename "garbage" to |
11 |
"list". |
12 |
|
13 |
BPO-19466 obtained from: |
14 |
https://github.com/python/cpython/commit/9ad58acbe8b90b4d0f2d2e139e38bb5aa32b7fb6 |
15 |
|
16 |
|
17 |
From: Victor Stinner <vstinner@python.org> |
18 |
Date: Sat, 1 Feb 2020 02:30:25 +0100 |
19 |
Subject: [PATCH] bpo-39511: PyThreadState_Clear() calls on_delete (GH-18296) |
20 |
|
21 |
PyThreadState.on_delete is a callback used to notify Python when a |
22 |
thread completes. _thread._set_sentinel() function creates a lock |
23 |
which is released when the thread completes. It sets on_delete |
24 |
callback to the internal release_sentinel() function. This lock is |
25 |
known as Threading._tstate_lock in the threading module. |
26 |
|
27 |
The release_sentinel() function uses the Python C API. The problem is |
28 |
that on_delete is called late in the Python finalization, when the C |
29 |
API is no longer fully working. |
30 |
|
31 |
The PyThreadState_Clear() function now calls the |
32 |
PyThreadState.on_delete callback. Previously, that happened in |
33 |
PyThreadState_Delete(). |
34 |
|
35 |
The release_sentinel() function is now called when the C API is still |
36 |
fully working. |
37 |
|
38 |
BPO-39511 obtained from: |
39 |
https://github.com/python/cpython/commit/4d96b4635aeff1b8ad41d41422ce808ce0b971c8 |
40 |
|
41 |
--- Lib/test/test_threading.py.orig 2021-04-02 10:32:10 UTC |
42 |
+++ Lib/test/test_threading.py |
43 |
@@ -762,6 +762,51 @@ class ThreadTests(BaseTestCase): |
44 |
# Daemon threads must never add it to _shutdown_locks. |
45 |
self.assertNotIn(tstate_lock, threading._shutdown_locks) |
46 |
|
47 |
+ def test_locals_at_exit(self): |
48 |
+ # bpo-19466: thread locals must not be deleted before destructors |
49 |
+ # are called |
50 |
+ rc, out, err = assert_python_ok("-c", """if 1: |
51 |
+ import threading |
52 |
+ |
53 |
+ class Atexit: |
54 |
+ def __del__(self): |
55 |
+ print("thread_dict.atexit = %r" % thread_dict.atexit) |
56 |
+ |
57 |
+ thread_dict = threading.local() |
58 |
+ thread_dict.atexit = "value" |
59 |
+ |
60 |
+ atexit = Atexit() |
61 |
+ """) |
62 |
+ self.assertEqual(out.rstrip(), b"thread_dict.atexit = 'value'") |
63 |
+ |
64 |
+ def test_warnings_at_exit(self): |
65 |
+ # bpo-19466: try to call most destructors at Python shutdown before |
66 |
+ # destroying Python thread states |
67 |
+ filename = __file__ |
68 |
+ rc, out, err = assert_python_ok("-Wd", "-c", """if 1: |
69 |
+ import time |
70 |
+ import threading |
71 |
+ from test import support |
72 |
+ |
73 |
+ def open_sleep(): |
74 |
+ # a warning will be emitted when the open file will be |
75 |
+ # destroyed (without being explicitly closed) while the daemon |
76 |
+ # thread is destroyed |
77 |
+ fileobj = open(%a, 'rb') |
78 |
+ start_event.set() |
79 |
+ time.sleep(support.LONG_TIMEOUT) |
80 |
+ |
81 |
+ start_event = threading.Event() |
82 |
+ |
83 |
+ thread = threading.Thread(target=open_sleep, daemon=True) |
84 |
+ thread.start() |
85 |
+ |
86 |
+ # wait until the thread started |
87 |
+ start_event.wait() |
88 |
+ """ % filename) |
89 |
+ self.assertRegex(err.rstrip(), |
90 |
+ b"^sys:1: ResourceWarning: unclosed file ") |
91 |
+ |
92 |
|
93 |
class ThreadJoinOnShutdown(BaseTestCase): |
94 |
|
95 |
--- Python/pylifecycle.c.orig 2021-04-02 10:32:10 UTC |
96 |
+++ Python/pylifecycle.c |
97 |
@@ -1196,6 +1196,16 @@ Py_FinalizeEx(void) |
98 |
runtime->initialized = 0; |
99 |
runtime->core_initialized = 0; |
100 |
|
101 |
+ /* Destroy the state of all threads of the interpreter, except of the |
102 |
+ current thread. In practice, only daemon threads should still be alive, |
103 |
+ except if wait_for_thread_shutdown() has been cancelled by CTRL+C. |
104 |
+ Clear frames of other threads to call objects destructors. Destructors |
105 |
+ will be called in the current Python thread. Since |
106 |
+ _PyRuntimeState_SetFinalizing() has been called, no other Python thread |
107 |
+ can take the GIL at this point: if they try, they will exit |
108 |
+ immediately. */ |
109 |
+ _PyThreadState_DeleteExcept(runtime, tstate); |
110 |
+ |
111 |
/* Flush sys.stdout and sys.stderr */ |
112 |
if (flush_std_files() < 0) { |
113 |
status = -1; |
114 |
--- Python/pystate.c.orig 2021-04-02 10:32:10 UTC |
115 |
+++ Python/pystate.c |
116 |
@@ -802,6 +802,10 @@ PyThreadState_Clear(PyThreadState *tstate) |
117 |
Py_CLEAR(tstate->async_gen_finalizer); |
118 |
|
119 |
Py_CLEAR(tstate->context); |
120 |
+ |
121 |
+ if (tstate->on_delete != NULL) { |
122 |
+ tstate->on_delete(tstate->on_delete_data); |
123 |
+ } |
124 |
} |
125 |
|
126 |
|
127 |
@@ -824,9 +828,7 @@ tstate_delete_common(_PyRuntimeState *runtime, PyThrea |
128 |
if (tstate->next) |
129 |
tstate->next->prev = tstate->prev; |
130 |
HEAD_UNLOCK(runtime); |
131 |
- if (tstate->on_delete != NULL) { |
132 |
- tstate->on_delete(tstate->on_delete_data); |
133 |
- } |
134 |
+ |
135 |
PyMem_RawFree(tstate); |
136 |
} |
137 |
|
138 |
@@ -890,25 +892,30 @@ void |
139 |
_PyThreadState_DeleteExcept(_PyRuntimeState *runtime, PyThreadState *tstate) |
140 |
{ |
141 |
PyInterpreterState *interp = tstate->interp; |
142 |
- PyThreadState *p, *next, *garbage; |
143 |
+ |
144 |
HEAD_LOCK(runtime); |
145 |
/* Remove all thread states, except tstate, from the linked list of |
146 |
thread states. This will allow calling PyThreadState_Clear() |
147 |
without holding the lock. */ |
148 |
- garbage = interp->tstate_head; |
149 |
- if (garbage == tstate) |
150 |
- garbage = tstate->next; |
151 |
- if (tstate->prev) |
152 |
+ PyThreadState *list = interp->tstate_head; |
153 |
+ if (list == tstate) { |
154 |
+ list = tstate->next; |
155 |
+ } |
156 |
+ if (tstate->prev) { |
157 |
tstate->prev->next = tstate->next; |
158 |
- if (tstate->next) |
159 |
+ } |
160 |
+ if (tstate->next) { |
161 |
tstate->next->prev = tstate->prev; |
162 |
+ } |
163 |
tstate->prev = tstate->next = NULL; |
164 |
interp->tstate_head = tstate; |
165 |
HEAD_UNLOCK(runtime); |
166 |
+ |
167 |
/* Clear and deallocate all stale thread states. Even if this |
168 |
executes Python code, we should be safe since it executes |
169 |
in the current thread, not one of the stale threads. */ |
170 |
- for (p = garbage; p; p = next) { |
171 |
+ PyThreadState *p, *next; |
172 |
+ for (p = list; p; p = next) { |
173 |
next = p->next; |
174 |
PyThreadState_Clear(p); |
175 |
PyMem_RawFree(p); |