Skip to content

Commit 0355566

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

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
@@ -417,8 +417,7 @@ Initializing and finalizing the interpreter
417417
Undo all initializations made by :c:func:`Py_Initialize` and subsequent use of
418418
Python/C API functions, and destroy all sub-interpreters (see
419419
:c:func:`Py_NewInterpreter` below) that were created and not yet destroyed since
420-
the last call to :c:func:`Py_Initialize`. Ideally, this frees all memory
421-
allocated by the Python interpreter. This is a no-op when called for a second
420+
the last call to :c:func:`Py_Initialize`. This is a no-op when called for a second
422421
time (without calling :c:func:`Py_Initialize` again first).
423422
424423
Since this is the reverse of :c:func:`Py_Initialize`, it should be called
@@ -430,6 +429,12 @@ Initializing and finalizing the interpreter
430429
If there were errors during finalization (flushing buffered data),
431430
``-1`` is returned.
432431
432+
Note that Python will do a best effort at freeing all memory allocated by the Python
433+
interpreter. Therefore, any C-Extension should make sure to correctly clean up all
434+
of the preveiously allocated PyObjects before using them in subsequent calls to
435+
:c:func:`Py_Initialize`. Otherwise it could introduce vulnerabilities and incorrect
436+
behavior.
437+
433438
This function is provided for a number of reasons. An embedding application
434439
might want to restart Python without having to restart the application itself.
435440
An application that has loaded the Python interpreter from a dynamically
@@ -444,10 +449,11 @@ Initializing and finalizing the interpreter
444449
loaded extension modules loaded by Python are not unloaded. Small amounts of
445450
memory allocated by the Python interpreter may not be freed (if you find a leak,
446451
please report it). Memory tied up in circular references between objects is not
447-
freed. Some memory allocated by extension modules may not be freed. Some
448-
extensions may not work properly if their initialization routine is called more
449-
than once; this can happen if an application calls :c:func:`Py_Initialize` and
450-
:c:func:`Py_FinalizeEx` more than once.
452+
freed. Interned strings will all be deallocated regarldess of their reference count.
453+
Some memory allocated by extension modules may not be freed. Some extensions may not
454+
work properly if their initialization routine is called more than once; this can
455+
happen if an application calls :c:func:`Py_Initialize` and :c:func:`Py_FinalizeEx`
456+
more than once.
451457
452458
.. audit-event:: cpython._PySys_ClearAuditHooks "" c.Py_FinalizeEx
453459

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
@@ -15404,19 +15404,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp)
1540415404
int shared = 0;
1540515405
switch (PyUnicode_CHECK_INTERNED(s)) {
1540615406
case SSTATE_INTERNED_IMMORTAL:
15407-
/* Make immortal interned strings mortal again.
15408-
*
15409-
* Currently, the runtime is not able to guarantee that it can exit
15410-
* without allocations that carry over to a future initialization
15411-
* of Python within the same process. i.e:
15412-
* ./python -X showrefcount -c 'import itertools'
15413-
* [237 refs, 237 blocks]
15414-
*
15415-
* This should remain disabled (`Py_DEBUG` only) until there is a
15416-
* strict guarantee that no memory will be left after
15417-
* `Py_Finalize`.
15418-
*/
15419-
#ifdef Py_DEBUG
15407+
/* Make immortal interned strings mortal again. */
1542015408
// Skip the Immortal Instance check and restore
1542115409
// the two references (key and value) ignored
1542215410
// by PyUnicode_InternInPlace().
@@ -15429,7 +15417,6 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp)
1542915417
#ifdef INTERNED_STATS
1543015418
total_length += PyUnicode_GET_LENGTH(s);
1543115419
#endif
15432-
#endif // Py_DEBUG
1543315420
break;
1543415421
case SSTATE_INTERNED_IMMORTAL_STATIC:
1543515422
/* It is shared between interpreters, so we should unmark it

0 commit comments

Comments
 (0)