File tree Expand file tree Collapse file tree 7 files changed +96
-3
lines changed
Misc/NEWS.d/next/Core and Builtins Expand file tree Collapse file tree 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);
501501#else
502502# error "no available pyatomic implementation for this platform/compiler"
503503#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 {
9393 and _PyInterpreterState_SetFinalizing()
9494 to access it, don't access it directly. */
9595 _Py_atomic_address _finalizing ;
96+ /* The ID of the OS thread in which we are finalizing. */
97+ unsigned long _finalizing_id ;
9698
9799 struct _gc_runtime_state gc ;
98100
@@ -215,9 +217,23 @@ _PyInterpreterState_GetFinalizing(PyInterpreterState *interp) {
215217 return (PyThreadState * )_Py_atomic_load_relaxed (& interp -> _finalizing );
216218}
217219
220+ static inline unsigned long
221+ _PyInterpreterState_GetFinalizingID (PyInterpreterState * interp ) {
222+ return _Py_atomic_load_ulong_relaxed (& interp -> _finalizing_id );
223+ }
224+
218225static inline void
219226_PyInterpreterState_SetFinalizing (PyInterpreterState * interp , PyThreadState * tstate ) {
220227 _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+ }
221237}
222238
223239
Original file line number Diff line number Diff line change @@ -36,8 +36,12 @@ _Py_IsMainInterpreter(PyInterpreterState *interp)
3636static inline int
3737_Py_IsMainInterpreterFinalizing (PyInterpreterState * interp )
3838{
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 );
4145}
4246
4347
Original file line number Diff line number Diff line change @@ -171,6 +171,8 @@ typedef struct pyruntimestate {
171171 Use _PyRuntimeState_GetFinalizing() and _PyRuntimeState_SetFinalizing()
172172 to access it, don't access it directly. */
173173 _Py_atomic_address _finalizing ;
174+ /* The ID of the OS thread in which we are finalizing. */
175+ unsigned long _finalizing_id ;
174176
175177 struct pyinterpreters {
176178 PyThread_type_lock mutex ;
@@ -303,9 +305,23 @@ _PyRuntimeState_GetFinalizing(_PyRuntimeState *runtime) {
303305 return (PyThreadState * )_Py_atomic_load_relaxed (& runtime -> _finalizing );
304306}
305307
308+ static inline unsigned long
309+ _PyRuntimeState_GetFinalizingID (_PyRuntimeState * runtime ) {
310+ return _Py_atomic_load_ulong_relaxed (& runtime -> _finalizing_id );
311+ }
312+
306313static inline void
307314_PyRuntimeState_SetFinalizing (_PyRuntimeState * runtime , PyThreadState * tstate ) {
308315 _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+ }
309325}
310326
311327#ifdef __cplusplus
Original file line number Diff line number Diff line change 11import contextlib
22import os
3+ import sys
34import threading
45from textwrap import dedent
56import unittest
@@ -487,6 +488,26 @@ def task():
487488 pass
488489
489490
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+
490511class TestIsShareable (TestBase ):
491512
492513 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)
29642964 tstate->interp->runtime to support calls from Python daemon threads.
29652965 After Py_Finalize() has been called, tstate can be a dangling pointer:
29662966 point to PyThreadState freed memory. */
2967+ unsigned long finalizing_id = _PyRuntimeState_GetFinalizingID (& _PyRuntime );
29672968 PyThreadState * finalizing = _PyRuntimeState_GetFinalizing (& _PyRuntime );
29682969 if (finalizing == NULL ) {
2970+ // XXX This isn't completely safe from daemon thraeds,
2971+ // since tstate might be a dangling pointer.
29692972 finalizing = _PyInterpreterState_GetFinalizing (tstate -> interp );
2973+ finalizing_id = _PyInterpreterState_GetFinalizingID (tstate -> interp );
29702974 }
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 ;
29722987}
29732988
29742989
You can’t perform that action at this time.
0 commit comments