Skip to content

Commit dccc2ed

Browse files
eduardo-elizondohugovk
authored andcommitted
[3.12] pythongh-113190: Reenable non-debug interned string cleanup (pythonGH-113601)
(cherry picked from commit 3203a74) Co-authored-by: Eddie Elizondo <[email protected]>
1 parent 6a268a0 commit dccc2ed

File tree

4 files changed

+32
-42
lines changed

4 files changed

+32
-42
lines changed

Doc/c-api/init.rst

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,7 @@ Initializing and finalizing the interpreter
386386
Undo all initializations made by :c:func:`Py_Initialize` and subsequent use of
387387
Python/C API functions, and destroy all sub-interpreters (see
388388
:c:func:`Py_NewInterpreter` below) that were created and not yet destroyed since
389-
the last call to :c:func:`Py_Initialize`. Ideally, this frees all memory
390-
allocated by the Python interpreter. This is a no-op when called for a second
389+
the last call to :c:func:`Py_Initialize`. This is a no-op when called for a second
391390
time (without calling :c:func:`Py_Initialize` again first).
392391
393392
Since this is the reverse of :c:func:`Py_Initialize`, it should be called
@@ -399,6 +398,12 @@ Initializing and finalizing the interpreter
399398
If there were errors during finalization (flushing buffered data),
400399
``-1`` is returned.
401400
401+
Note that Python will do a best effort at freeing all memory allocated by the Python
402+
interpreter. Therefore, any C-Extension should make sure to correctly clean up all
403+
of the preveiously allocated PyObjects before using them in subsequent calls to
404+
:c:func:`Py_Initialize`. Otherwise it could introduce vulnerabilities and incorrect
405+
behavior.
406+
402407
This function is provided for a number of reasons. An embedding application
403408
might want to restart Python without having to restart the application itself.
404409
An application that has loaded the Python interpreter from a dynamically
@@ -413,10 +418,11 @@ Initializing and finalizing the interpreter
413418
loaded extension modules loaded by Python are not unloaded. Small amounts of
414419
memory allocated by the Python interpreter may not be freed (if you find a leak,
415420
please report it). Memory tied up in circular references between objects is not
416-
freed. Some memory allocated by extension modules may not be freed. Some
417-
extensions may not work properly if their initialization routine is called more
418-
than once; this can happen if an application calls :c:func:`Py_Initialize` and
419-
:c:func:`Py_FinalizeEx` more than once.
421+
freed. Interned strings will all be deallocated regarldess of their reference count.
422+
Some memory allocated by extension modules may not be freed. Some extensions may not
423+
work properly if their initialization routine is called more than once; this can
424+
happen if an application calls :c:func:`Py_Initialize` and :c:func:`Py_FinalizeEx`
425+
more than once.
420426

421427
.. audit-event:: cpython._PySys_ClearAuditHooks "" c.Py_FinalizeEx
422428

Lib/test/_test_embed_structseq.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
import sys
22
import types
3+
import unittest
34

4-
# Note: This test file can't import `unittest` since the runtime can't
5-
# currently guarantee that it will not leak memory. Doing so will mark
6-
# the test as passing but with reference leaks. This can safely import
7-
# the `unittest` library once there's a strict guarantee of no leaks
8-
# during runtime shutdown.
95

106
# bpo-46417: Test that structseq types used by the sys module are still
117
# valid when Py_Finalize()/Py_Initialize() are called multiple times.
12-
class TestStructSeq:
8+
class TestStructSeq(unittest.TestCase):
139
# test PyTypeObject members
14-
def _check_structseq(self, obj_type):
10+
def check_structseq(self, obj_type):
1511
# ob_refcnt
16-
assert sys.getrefcount(obj_type) > 1
12+
self.assertGreaterEqual(sys.getrefcount(obj_type), 1)
1713
# tp_base
18-
assert issubclass(obj_type, tuple)
14+
self.assertTrue(issubclass(obj_type, tuple))
1915
# tp_bases
20-
assert obj_type.__bases__ == (tuple,)
16+
self.assertEqual(obj_type.__bases__, (tuple,))
2117
# tp_dict
22-
assert isinstance(obj_type.__dict__, types.MappingProxyType)
18+
self.assertIsInstance(obj_type.__dict__, types.MappingProxyType)
2319
# tp_mro
24-
assert obj_type.__mro__ == (obj_type, tuple, object)
20+
self.assertEqual(obj_type.__mro__, (obj_type, tuple, object))
2521
# tp_name
26-
assert isinstance(type.__name__, str)
22+
self.assertIsInstance(type.__name__, str)
2723
# tp_subclasses
28-
assert obj_type.__subclasses__() == []
24+
self.assertEqual(obj_type.__subclasses__(), [])
2925

3026
def test_sys_attrs(self):
3127
for attr_name in (
@@ -36,23 +32,23 @@ def test_sys_attrs(self):
3632
'thread_info', # ThreadInfoType
3733
'version_info', # VersionInfoType
3834
):
39-
attr = getattr(sys, attr_name)
40-
self._check_structseq(type(attr))
35+
with self.subTest(attr=attr_name):
36+
attr = getattr(sys, attr_name)
37+
self.check_structseq(type(attr))
4138

4239
def test_sys_funcs(self):
4340
func_names = ['get_asyncgen_hooks'] # AsyncGenHooksType
4441
if hasattr(sys, 'getwindowsversion'):
4542
func_names.append('getwindowsversion') # WindowsVersionType
4643
for func_name in func_names:
47-
func = getattr(sys, func_name)
48-
obj = func()
49-
self._check_structseq(type(obj))
44+
with self.subTest(func=func_name):
45+
func = getattr(sys, func_name)
46+
obj = func()
47+
self.check_structseq(type(obj))
5048

5149

5250
try:
53-
tests = TestStructSeq()
54-
tests.test_sys_attrs()
55-
tests.test_sys_funcs()
51+
unittest.main()
5652
except SystemExit as exc:
5753
if exc.args[0] != 0:
5854
raise
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:c:func:`Py_Finalize` now deletes all interned strings.

Objects/unicodeobject.c

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15201,19 +15201,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp)
1520115201
int shared = 0;
1520215202
switch (PyUnicode_CHECK_INTERNED(s)) {
1520315203
case SSTATE_INTERNED_IMMORTAL:
15204-
/* Make immortal interned strings mortal again.
15205-
*
15206-
* Currently, the runtime is not able to guarantee that it can exit
15207-
* without allocations that carry over to a future initialization
15208-
* of Python within the same process. i.e:
15209-
* ./python -X showrefcount -c 'import itertools'
15210-
* [237 refs, 237 blocks]
15211-
*
15212-
* This should remain disabled (`Py_DEBUG` only) until there is a
15213-
* strict guarantee that no memory will be left after
15214-
* `Py_Finalize`.
15215-
*/
15216-
#ifdef Py_DEBUG
15204+
/* Make immortal interned strings mortal again. */
1521715205
// Skip the Immortal Instance check and restore
1521815206
// the two references (key and value) ignored
1521915207
// by PyUnicode_InternInPlace().
@@ -15226,7 +15214,6 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp)
1522615214
#ifdef INTERNED_STATS
1522715215
total_length += PyUnicode_GET_LENGTH(s);
1522815216
#endif
15229-
#endif // Py_DEBUG
1523015217
break;
1523115218
case SSTATE_INTERNED_IMMORTAL_STATIC:
1523215219
/* It is shared between interpreters, so we should unmark it

0 commit comments

Comments
 (0)