File tree 7 files changed +96
-3
lines changed
Misc/NEWS.d/next/Core and Builtins
7 files changed +96
-3
lines changed Original file line number Diff line number Diff line change @@ -501,3 +501,20 @@ static inline void _Py_atomic_fence_release(void);
501
501
#else
502
502
# error "no available pyatomic implementation for this platform/compiler"
503
503
#endif
504
+
505
+
506
+ // --- aliases ---------------------------------------------------------------
507
+
508
+ #if SIZEOF_LONG == 8
509
+ # define _Py_atomic_load_ulong _Py_atomic_load_uint64
510
+ # define _Py_atomic_load_ulong_relaxed _Py_atomic_load_uint64_relaxed
511
+ # define _Py_atomic_store_ulong _Py_atomic_store_uint64
512
+ # define _Py_atomic_store_ulong_relaxed _Py_atomic_store_uint64_relaxed
513
+ #elif SIZEOF_LONG == 4
514
+ # define _Py_atomic_load_ulong _Py_atomic_load_uint32
515
+ # define _Py_atomic_load_ulong_relaxed _Py_atomic_load_uint32_relaxed
516
+ # define _Py_atomic_store_ulong _Py_atomic_store_uint32
517
+ # define _Py_atomic_store_ulong_relaxed _Py_atomic_store_uint32_relaxed
518
+ #else
519
+ # error "long must be 4 or 8 bytes in size"
520
+ #endif // SIZEOF_LONG
Original file line number Diff line number Diff line change @@ -93,6 +93,8 @@ struct _is {
93
93
and _PyInterpreterState_SetFinalizing()
94
94
to access it, don't access it directly. */
95
95
_Py_atomic_address _finalizing ;
96
+ /* The ID of the OS thread in which we are finalizing. */
97
+ unsigned long _finalizing_id ;
96
98
97
99
struct _gc_runtime_state gc ;
98
100
@@ -215,9 +217,23 @@ _PyInterpreterState_GetFinalizing(PyInterpreterState *interp) {
215
217
return (PyThreadState * )_Py_atomic_load_relaxed (& interp -> _finalizing );
216
218
}
217
219
220
+ static inline unsigned long
221
+ _PyInterpreterState_GetFinalizingID (PyInterpreterState * interp ) {
222
+ return _Py_atomic_load_ulong_relaxed (& interp -> _finalizing_id );
223
+ }
224
+
218
225
static inline void
219
226
_PyInterpreterState_SetFinalizing (PyInterpreterState * interp , PyThreadState * tstate ) {
220
227
_Py_atomic_store_relaxed (& interp -> _finalizing , (uintptr_t )tstate );
228
+ if (tstate == NULL ) {
229
+ _Py_atomic_store_ulong_relaxed (& interp -> _finalizing_id , 0 );
230
+ }
231
+ else {
232
+ // XXX Re-enable this assert once gh-109860 is fixed.
233
+ //assert(tstate->thread_id == PyThread_get_thread_ident());
234
+ _Py_atomic_store_ulong_relaxed (& interp -> _finalizing_id ,
235
+ tstate -> thread_id );
236
+ }
221
237
}
222
238
223
239
Original file line number Diff line number Diff line change @@ -36,8 +36,12 @@ _Py_IsMainInterpreter(PyInterpreterState *interp)
36
36
static inline int
37
37
_Py_IsMainInterpreterFinalizing (PyInterpreterState * interp )
38
38
{
39
- return (_PyRuntimeState_GetFinalizing (interp -> runtime ) != NULL &&
40
- interp == & interp -> runtime -> _main_interpreter );
39
+ /* bpo-39877: Access _PyRuntime directly rather than using
40
+ tstate->interp->runtime to support calls from Python daemon threads.
41
+ After Py_Finalize() has been called, tstate can be a dangling pointer:
42
+ point to PyThreadState freed memory. */
43
+ return (_PyRuntimeState_GetFinalizing (& _PyRuntime ) != NULL &&
44
+ interp == & _PyRuntime ._main_interpreter );
41
45
}
42
46
43
47
Original file line number Diff line number Diff line change @@ -171,6 +171,8 @@ typedef struct pyruntimestate {
171
171
Use _PyRuntimeState_GetFinalizing() and _PyRuntimeState_SetFinalizing()
172
172
to access it, don't access it directly. */
173
173
_Py_atomic_address _finalizing ;
174
+ /* The ID of the OS thread in which we are finalizing. */
175
+ unsigned long _finalizing_id ;
174
176
175
177
struct pyinterpreters {
176
178
PyThread_type_lock mutex ;
@@ -303,9 +305,23 @@ _PyRuntimeState_GetFinalizing(_PyRuntimeState *runtime) {
303
305
return (PyThreadState * )_Py_atomic_load_relaxed (& runtime -> _finalizing );
304
306
}
305
307
308
+ static inline unsigned long
309
+ _PyRuntimeState_GetFinalizingID (_PyRuntimeState * runtime ) {
310
+ return _Py_atomic_load_ulong_relaxed (& runtime -> _finalizing_id );
311
+ }
312
+
306
313
static inline void
307
314
_PyRuntimeState_SetFinalizing (_PyRuntimeState * runtime , PyThreadState * tstate ) {
308
315
_Py_atomic_store_relaxed (& runtime -> _finalizing , (uintptr_t )tstate );
316
+ if (tstate == NULL ) {
317
+ _Py_atomic_store_ulong_relaxed (& runtime -> _finalizing_id , 0 );
318
+ }
319
+ else {
320
+ // XXX Re-enable this assert once gh-109860 is fixed.
321
+ //assert(tstate->thread_id == PyThread_get_thread_ident());
322
+ _Py_atomic_store_ulong_relaxed (& runtime -> _finalizing_id ,
323
+ tstate -> thread_id );
324
+ }
309
325
}
310
326
311
327
#ifdef __cplusplus
Original file line number Diff line number Diff line change 1
1
import contextlib
2
2
import os
3
+ import sys
3
4
import threading
4
5
from textwrap import dedent
5
6
import unittest
@@ -487,6 +488,26 @@ def task():
487
488
pass
488
489
489
490
491
+ class FinalizationTests (TestBase ):
492
+
493
+ def test_gh_109793 (self ):
494
+ import subprocess
495
+ argv = [sys .executable , '-c' , '''if True:
496
+ import _xxsubinterpreters as _interpreters
497
+ interpid = _interpreters.create()
498
+ raise Exception
499
+ ''' ]
500
+ proc = subprocess .run (argv , capture_output = True , text = True )
501
+ self .assertIn ('Traceback' , proc .stderr )
502
+ if proc .returncode == 0 and support .verbose :
503
+ print ()
504
+ print ("--- cmd unexpected succeeded ---" )
505
+ print (f"stdout:\n { proc .stdout } " )
506
+ print (f"stderr:\n { proc .stderr } " )
507
+ print ("------" )
508
+ self .assertEqual (proc .returncode , 1 )
509
+
510
+
490
511
class TestIsShareable (TestBase ):
491
512
492
513
def test_default_shareables (self ):
Original file line number Diff line number Diff line change
1
+ The main thread no longer exits prematurely when a subinterpreter
2
+ is cleaned up during runtime finalization. The bug was a problem
3
+ particularly because, when triggered, the Python process would
4
+ always return with a 0 exitcode, even if it failed.
Original file line number Diff line number Diff line change @@ -2964,11 +2964,26 @@ _PyThreadState_MustExit(PyThreadState *tstate)
2964
2964
tstate->interp->runtime to support calls from Python daemon threads.
2965
2965
After Py_Finalize() has been called, tstate can be a dangling pointer:
2966
2966
point to PyThreadState freed memory. */
2967
+ unsigned long finalizing_id = _PyRuntimeState_GetFinalizingID (& _PyRuntime );
2967
2968
PyThreadState * finalizing = _PyRuntimeState_GetFinalizing (& _PyRuntime );
2968
2969
if (finalizing == NULL ) {
2970
+ // XXX This isn't completely safe from daemon thraeds,
2971
+ // since tstate might be a dangling pointer.
2969
2972
finalizing = _PyInterpreterState_GetFinalizing (tstate -> interp );
2973
+ finalizing_id = _PyInterpreterState_GetFinalizingID (tstate -> interp );
2970
2974
}
2971
- return (finalizing != NULL && finalizing != tstate );
2975
+ // XXX else check &_PyRuntime._main_interpreter._initial_thread
2976
+ if (finalizing == NULL ) {
2977
+ return 0 ;
2978
+ }
2979
+ else if (finalizing == tstate ) {
2980
+ return 0 ;
2981
+ }
2982
+ else if (finalizing_id == PyThread_get_thread_ident ()) {
2983
+ /* gh-109793: we must have switched interpreters. */
2984
+ return 0 ;
2985
+ }
2986
+ return 1 ;
2972
2987
}
2973
2988
2974
2989
You can’t perform that action at this time.
0 commit comments