Skip to content

Commit e9e3eab

Browse files
authored
bpo-46417: Finalize structseq types at exit (GH-30645)
Add _PyStructSequence_FiniType() and _PyStaticType_Dealloc() functions to finalize a structseq static type in Py_Finalize(). Currrently, these functions do nothing if Python is built in release mode. Clear static types: * AsyncGenHooksType: sys.set_asyncgen_hooks() * FlagsType: sys.flags * FloatInfoType: sys.float_info * Hash_InfoType: sys.hash_info * Int_InfoType: sys.int_info * ThreadInfoType: sys.thread_info * UnraisableHookArgsType: sys.unraisablehook * VersionInfoType: sys.version * WindowsVersionType: sys.getwindowsversion()
1 parent 27df756 commit e9e3eab

17 files changed

+230
-2
lines changed

Include/internal/pycore_floatobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extern "C" {
1414
extern void _PyFloat_InitState(PyInterpreterState *);
1515
extern PyStatus _PyFloat_InitTypes(PyInterpreterState *);
1616
extern void _PyFloat_Fini(PyInterpreterState *);
17+
extern void _PyFloat_FiniType(PyInterpreterState *);
1718

1819

1920
/* other API */

Include/internal/pycore_long.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern "C" {
1515
/* runtime lifecycle */
1616

1717
extern PyStatus _PyLong_InitTypes(PyInterpreterState *);
18+
extern void _PyLong_FiniTypes(PyInterpreterState *interp);
1819

1920

2021
/* other API */

Include/internal/pycore_pyerrors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern "C" {
1212
/* runtime lifecycle */
1313

1414
extern PyStatus _PyErr_InitTypes(PyInterpreterState *);
15+
extern void _PyErr_FiniTypes(PyInterpreterState *);
1516

1617

1718
/* other API */

Include/internal/pycore_pylifecycle.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ extern PyStatus _PySys_Create(
5858
extern PyStatus _PySys_ReadPreinitWarnOptions(PyWideStringList *options);
5959
extern PyStatus _PySys_ReadPreinitXOptions(PyConfig *config);
6060
extern int _PySys_UpdateConfig(PyThreadState *tstate);
61+
extern void _PySys_Fini(PyInterpreterState *interp);
6162
extern PyStatus _PyBuiltins_AddExceptions(PyObject * bltinmod);
6263
extern PyStatus _Py_HashRandomization_Init(const PyConfig *);
6364

@@ -81,6 +82,7 @@ extern void _PyTraceMalloc_Fini(void);
8182
extern void _PyWarnings_Fini(PyInterpreterState *interp);
8283
extern void _PyAST_Fini(PyInterpreterState *interp);
8384
extern void _PyAtExit_Fini(PyInterpreterState *interp);
85+
extern void _PyThread_FiniType(PyInterpreterState *interp);
8486

8587
extern PyStatus _PyGILState_Init(_PyRuntimeState *runtime);
8688
extern PyStatus _PyGILState_SetTstate(PyThreadState *tstate);

Include/internal/pycore_typeobject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct type_cache {
4040

4141
extern PyStatus _PyTypes_InitSlotDefs(void);
4242

43+
extern void _PyStaticType_Dealloc(PyTypeObject *type);
44+
4345

4446
#ifdef __cplusplus
4547
}

Include/structseq.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ PyAPI_FUNC(void) PyStructSequence_InitType(PyTypeObject *type,
2727
PyAPI_FUNC(int) PyStructSequence_InitType2(PyTypeObject *type,
2828
PyStructSequence_Desc *desc);
2929
#endif
30+
#ifdef Py_BUILD_CORE
31+
PyAPI_FUNC(void) _PyStructSequence_FiniType(PyTypeObject *type);
32+
#endif
3033
PyAPI_FUNC(PyTypeObject*) PyStructSequence_NewType(PyStructSequence_Desc *desc);
3134

3235
PyAPI_FUNC(PyObject *) PyStructSequence_New(PyTypeObject* type);

Lib/test/_test_embed_structseq.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import sys
2+
import types
3+
import unittest
4+
5+
6+
# bpo-46417: Test that structseq types used by the sys module are still
7+
# valid when Py_Finalize()/Py_Initialize() are called multiple times.
8+
class TestStructSeq(unittest.TestCase):
9+
# test PyTypeObject members
10+
def check_structseq(self, obj_type):
11+
# ob_refcnt
12+
self.assertGreaterEqual(sys.getrefcount(obj_type), 1)
13+
# tp_base
14+
self.assertTrue(issubclass(obj_type, tuple))
15+
# tp_bases
16+
self.assertEqual(obj_type.__bases__, (tuple,))
17+
# tp_dict
18+
self.assertIsInstance(obj_type.__dict__, types.MappingProxyType)
19+
# tp_mro
20+
self.assertEqual(obj_type.__mro__, (obj_type, tuple, object))
21+
# tp_name
22+
self.assertIsInstance(type.__name__, str)
23+
# tp_subclasses
24+
self.assertEqual(obj_type.__subclasses__(), [])
25+
26+
def test_sys_attrs(self):
27+
for attr_name in (
28+
'flags', # FlagsType
29+
'float_info', # FloatInfoType
30+
'hash_info', # Hash_InfoType
31+
'int_info', # Int_InfoType
32+
'thread_info', # ThreadInfoType
33+
'version_info', # VersionInfoType
34+
):
35+
with self.subTest(attr=attr_name):
36+
attr = getattr(sys, attr_name)
37+
self.check_structseq(type(attr))
38+
39+
def test_sys_funcs(self):
40+
func_names = ['get_asyncgen_hooks'] # AsyncGenHooksType
41+
if hasattr(sys, 'getwindowsversion'):
42+
func_names.append('getwindowsversion') # WindowsVersionType
43+
for func_name in func_names:
44+
with self.subTest(func=func_name):
45+
func = getattr(sys, func_name)
46+
obj = func()
47+
self.check_structseq(type(obj))
48+
49+
50+
try:
51+
unittest.main()
52+
except SystemExit as exc:
53+
if exc.args[0] != 0:
54+
raise
55+
print("Tests passed")

Lib/test/test_embed.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,18 @@ def test_run_main_loop(self):
329329
self.assertEqual(out, "Py_RunMain(): sys.argv=['-c', 'arg2']\n" * nloop)
330330
self.assertEqual(err, '')
331331

332+
def test_finalize_structseq(self):
333+
# bpo-46417: Py_Finalize() clears structseq static types. Check that
334+
# sys attributes using struct types still work when
335+
# Py_Finalize()/Py_Initialize() is called multiple times.
336+
# print() calls type->tp_repr(instance) and so checks that the types
337+
# are still working properly.
338+
script = support.findfile('_test_embed_structseq.py')
339+
with open(script, encoding="utf-8") as fp:
340+
code = fp.read()
341+
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
342+
self.assertEqual(out, 'Tests passed\n' * INIT_LOOPS)
343+
332344

333345
class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
334346
maxDiff = 4096

Objects/floatobject.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,14 @@ _PyFloat_Fini(PyInterpreterState *interp)
20822082
#endif
20832083
}
20842084

2085+
void
2086+
_PyFloat_FiniType(PyInterpreterState *interp)
2087+
{
2088+
if (_Py_IsMainInterpreter(interp)) {
2089+
_PyStructSequence_FiniType(&FloatInfoType);
2090+
}
2091+
}
2092+
20852093
/* Print summary info about the state of the optimized allocator */
20862094
void
20872095
_PyFloat_DebugMallocStats(FILE *out)

Objects/longobject.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5949,3 +5949,14 @@ _PyLong_InitTypes(PyInterpreterState *interp)
59495949

59505950
return _PyStatus_OK();
59515951
}
5952+
5953+
5954+
void
5955+
_PyLong_FiniTypes(PyInterpreterState *interp)
5956+
{
5957+
if (!_Py_IsMainInterpreter(interp)) {
5958+
return;
5959+
}
5960+
5961+
_PyStructSequence_FiniType(&Int_InfoType);
5962+
}

0 commit comments

Comments
 (0)