View | Details | Raw Unified | Return to bug 255445
Collapse All | Expand All

(-)b/lang/python38/Makefile (+1 lines)
Lines 2-7 Link Here
2
2
3
PORTNAME=	python
3
PORTNAME=	python
4
PORTVERSION=	${PYTHON_PORTVERSION}
4
PORTVERSION=	${PYTHON_PORTVERSION}
5
PORTREVISION=	1
5
CATEGORIES=	lang python
6
CATEGORIES=	lang python
6
MASTER_SITES=	PYTHON/ftp/python/${PORTVERSION}
7
MASTER_SITES=	PYTHON/ftp/python/${PORTVERSION}
7
PKGNAMESUFFIX=	${PYTHON_SUFFIX}
8
PKGNAMESUFFIX=	${PYTHON_SUFFIX}
(-)b/lang/python38/files/patch-issue19466_39511 (+175 lines)
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);

Return to bug 255445