diff --git a/Include/boolobject.h b/Include/boolobject.h index cda6f89a99e9a2..76339fa6faf07c 100644 --- a/Include/boolobject.h +++ b/Include/boolobject.h @@ -31,8 +31,8 @@ PyAPI_FUNC(int) Py_IsFalse(PyObject *x); #define Py_IsFalse(x) Py_Is((x), Py_False) /* Macros for returning Py_True or Py_False, respectively */ -#define Py_RETURN_TRUE return Py_NewRef(Py_True) -#define Py_RETURN_FALSE return Py_NewRef(Py_False) +#define Py_RETURN_TRUE return Py_True +#define Py_RETURN_FALSE return Py_False /* Function to return a bool from a C long */ PyAPI_FUNC(PyObject *) PyBool_FromLong(long); diff --git a/Include/cpython/sysmodule.h b/Include/cpython/sysmodule.h index 27dff7b2e3d930..18ce0d889decd4 100644 --- a/Include/cpython/sysmodule.h +++ b/Include/cpython/sysmodule.h @@ -7,6 +7,8 @@ PyAPI_FUNC(PyObject *) _PySys_GetAttr(PyThreadState *tstate, PyAPI_FUNC(PyObject *) _PySys_GetObjectId(_Py_Identifier *key); PyAPI_FUNC(int) _PySys_SetObjectId(_Py_Identifier *key, PyObject *); +PyAPI_FUNC(PyObject *) _PySys_StdlibModuleNameList(void); + PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *); typedef int(*Py_AuditHookFunction)(const char *, PyObject *, void *); diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 65abc1884c3bbd..86fbb243570889 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -17,7 +17,7 @@ extern "C" { #define _PyObject_IMMORTAL_INIT(type) \ { \ - .ob_refcnt = 999999999, \ + .ob_refcnt = _Py_IMMORTAL_REFCNT, \ .ob_type = type, \ } #define _PyVarObject_IMMORTAL_INIT(type, size) \ diff --git a/Include/moduleobject.h b/Include/moduleobject.h index 49b116ca1c3587..a51771aebda13d 100644 --- a/Include/moduleobject.h +++ b/Include/moduleobject.h @@ -29,6 +29,7 @@ Py_DEPRECATED(3.2) PyAPI_FUNC(const char *) PyModule_GetFilename(PyObject *); PyAPI_FUNC(PyObject *) PyModule_GetFilenameObject(PyObject *); #ifndef Py_LIMITED_API PyAPI_FUNC(void) _PyModule_Clear(PyObject *); +PyAPI_FUNC(void) _PyModule_PhasedClear(PyObject *, int phase); PyAPI_FUNC(void) _PyModule_ClearDict(PyObject *); PyAPI_FUNC(int) _PyModuleSpec_IsInitializing(PyObject *); #endif @@ -48,11 +49,13 @@ typedef struct PyModuleDef_Base { PyObject* m_copy; } PyModuleDef_Base; -#define PyModuleDef_HEAD_INIT { \ - PyObject_HEAD_INIT(NULL) \ - NULL, /* m_init */ \ - 0, /* m_index */ \ - NULL, /* m_copy */ \ +// TODO(eduardo-elizondo): This is only used to simplify the review of GH-19474 +// Rather than changing this API, we'll introduce PyModuleDef_HEAD_IMMORTAL_INIT +#define PyModuleDef_HEAD_INIT { \ + PyObject_HEAD_IMMORTAL_INIT(NULL) \ + NULL, /* m_init */ \ + 0, /* m_index */ \ + NULL, /* m_copy */ \ } struct PyModuleDef_Slot; diff --git a/Include/object.h b/Include/object.h index 3566c736a535c4..95107cb576b45c 100644 --- a/Include/object.h +++ b/Include/object.h @@ -81,12 +81,34 @@ typedef struct _typeobject PyTypeObject; /* PyObject_HEAD defines the initial segment of every PyObject. */ #define PyObject_HEAD PyObject ob_base; +/* +Immortalization: + +This marks the reference count bit that will be used to define immortality. +The GC bit-shifts refcounts left by two, and after that shift it still needs +to be larger than zero, so it's placed after the first three high bits. + +For backwards compatibility the actual reference count of an immortal instance +is set to higher than just the immortal bit. This will ensure that the immortal +bit will remain active, even with extensions compiled without the updated checks +in Py_INCREF and Py_DECREF. This can be safely changed to a smaller value if +additional bits are needed in the reference count field. +*/ +#define _Py_IMMORTAL_BIT_OFFSET (8 * sizeof(Py_ssize_t) - 4) +#define _Py_IMMORTAL_BIT (1LL << _Py_IMMORTAL_BIT_OFFSET) +#define _Py_IMMORTAL_REFCNT (_Py_IMMORTAL_BIT + (_Py_IMMORTAL_BIT / 2)) + #define PyObject_HEAD_INIT(type) \ { _PyObject_EXTRA_INIT \ 1, type }, +#define PyObject_HEAD_IMMORTAL_INIT(type) \ + { _PyObject_EXTRA_INIT _Py_IMMORTAL_REFCNT, type }, + +// TODO(eduardo-elizondo): This is only used to simplify the review of GH-19474 +// Rather than changing this API, we'll introduce PyVarObject_HEAD_IMMORTAL_INIT #define PyVarObject_HEAD_INIT(type, size) \ - { PyObject_HEAD_INIT(type) size }, + { PyObject_HEAD_IMMORTAL_INIT(type) size }, /* PyObject_VAR_HEAD defines the initial segment of all variable-size * container objects. These end with a declaration of an array with 1 @@ -145,6 +167,20 @@ static inline Py_ssize_t Py_SIZE(const PyVarObject *ob) { } #define Py_SIZE(ob) Py_SIZE(_PyVarObject_CAST_CONST(ob)) +PyAPI_FUNC(PyObject *) _PyGC_ImmortalizeHeap(void); +PyAPI_FUNC(PyObject *) _PyGC_TransitiveImmortalize(PyObject *obj); + +static inline int _Py_IsImmortal(PyObject *op) +{ + return (op->ob_refcnt & _Py_IMMORTAL_BIT) != 0; +} + +static inline void _Py_SetImmortal(PyObject *op) +{ + if (op) { + op->ob_refcnt = _Py_IMMORTAL_REFCNT; + } +} static inline int Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { // bpo-44378: Don't use Py_TYPE() since Py_TYPE() requires a non-const @@ -155,6 +191,9 @@ static inline int Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { + if (_Py_IsImmortal(ob)) { + return; + } ob->ob_refcnt = refcnt; } #define Py_SET_REFCNT(ob, refcnt) Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) @@ -483,6 +522,9 @@ static inline void Py_INCREF(PyObject *op) #else // Non-limited C API and limited C API for Python 3.9 and older access // directly PyObject.ob_refcnt. + if (_Py_IsImmortal(op)) { + return; + } #ifdef Py_REF_DEBUG _Py_RefTotal++; #endif @@ -503,6 +545,9 @@ static inline void Py_DECREF( #else // Non-limited C API and limited C API for Python 3.9 and older access // directly PyObject.ob_refcnt. + if (_Py_IsImmortal(op)) { + return; + } #ifdef Py_REF_DEBUG _Py_RefTotal--; #endif @@ -627,7 +672,7 @@ PyAPI_FUNC(int) Py_IsNone(PyObject *x); #define Py_IsNone(x) Py_Is((x), Py_None) /* Macro for returning Py_None from a function */ -#define Py_RETURN_NONE return Py_NewRef(Py_None) +#define Py_RETURN_NONE return Py_None /* Py_NotImplemented is a singleton used to signal that an operation is diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index 695f7733305ed7..d38c973a106469 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -288,6 +288,8 @@ def __init__(self, executor): self.thread_wakeup = executor._executor_manager_thread_wakeup self.shutdown_lock = executor._shutdown_lock + # Make mp.util.debug available even during runtime shutdown + self._debugp = mp.util.debug # A weakref.ref to the ProcessPoolExecutor that owns this thread. Used # to determine if the ProcessPoolExecutor has been garbage collected # and that the manager can exit. @@ -297,8 +299,9 @@ def __init__(self, executor): def weakref_cb(_, thread_wakeup=self.thread_wakeup, shutdown_lock=self.shutdown_lock): - mp.util.debug('Executor collected: triggering callback for' + self._debugp('Executor collected: triggering callback for' ' QueueManager wakeup') + # ./python Lib/test/test_concurrent_futures.py ProcessPoolForkProcessPoolShutdownTest.test_interpreter_shutdown with shutdown_lock: thread_wakeup.wakeup() diff --git a/Lib/ctypes/test/test_python_api.py b/Lib/ctypes/test/test_python_api.py index 49571f97bbe152..de8989e2c3300f 100644 --- a/Lib/ctypes/test/test_python_api.py +++ b/Lib/ctypes/test/test_python_api.py @@ -46,7 +46,8 @@ def test_PyLong_Long(self): pythonapi.PyLong_AsLong.restype = c_long res = pythonapi.PyLong_AsLong(42) - self.assertEqual(grc(res), ref42 + 1) + # Small int refcnts don't change + self.assertEqual(grc(res), ref42) del res self.assertEqual(grc(42), ref42) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index e49e0d02a80cf0..b554c271043391 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1448,6 +1448,8 @@ def __init__(self, name, level=NOTSET): self.handlers = [] self.disabled = False self._cache = {} + # Make normcase available even during runtime shutdown + self._normcase = os.path.normcase def setLevel(self, level): """ @@ -1569,7 +1571,7 @@ def findCaller(self, stack_info=False, stacklevel=1): rv = "(unknown file)", 0, "(unknown function)", None while hasattr(f, "f_code"): co = f.f_code - filename = os.path.normcase(co.co_filename) + filename = self._normcase(co.co_filename) if filename == _srcfile: f = f.f_back continue diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index d97381926d47bc..130b9bcabe34bc 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -1334,10 +1334,14 @@ def __init__(self, *args, **kwargs): from . import resource_tracker resource_tracker.ensure_running() BaseManager.__init__(self, *args, **kwargs) - util.debug(f"{self.__class__.__name__} created by pid {getpid()}") + + # Make util.debug available even during runtime shutdown + self._debugp = util.debug + + self._debugp(f"{self.__class__.__name__} created by pid {getpid()}") def __del__(self): - util.debug(f"{self.__class__.__name__}.__del__ by pid {getpid()}") + self._debugp(f"{self.__class__.__name__}.__del__ by pid {getpid()}") def get_server(self): 'Better than monkeypatching for now; merge into Server ultimately' diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 531cbf32f1283f..2afac7710440c4 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -804,6 +804,9 @@ def __init__(self, suffix=None, prefix=None, dir=None, warn_message="Implicitly cleaning up {!r}".format(self), ignore_errors=self._ignore_cleanup_errors) + # Make _os.path.exists available even during runtime shutdown + self._exists = _os.path.exists + @classmethod def _rmtree(cls, name, ignore_errors=False): def onerror(func, path, exc_info): @@ -850,7 +853,7 @@ def __exit__(self, exc, value, tb): self.cleanup() def cleanup(self): - if self._finalizer.detach() or _os.path.exists(self.name): + if self._finalizer.detach() or self._exists(self.name): self._rmtree(self.name, ignore_errors=self._ignore_cleanup_errors) __class_getitem__ = classmethod(_types.GenericAlias) diff --git a/Lib/test/final_a.py b/Lib/test/final_a.py index 390ee8895a8a9e..76db1044e2a941 100644 --- a/Lib/test/final_a.py +++ b/Lib/test/final_a.py @@ -3,17 +3,18 @@ """ import shutil +import sys import test.final_b x = 'a' class C: - def __del__(self): + def __del__(self, sys=sys): # Inspect module globals and builtins - print("x =", x) - print("final_b.x =", test.final_b.x) - print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None)) - print("len =", getattr(len, '__name__', None)) + print("x =", x, file=sys.stderr) + print("final_b.x =", test.final_b.x, file=sys.stderr) + print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None), sys.stderr) + print("len =", getattr(len, '__name__', None), sys.stderr) c = C() _underscored = C() diff --git a/Lib/test/final_b.py b/Lib/test/final_b.py index 7228d82b880156..cf11fa28d7889d 100644 --- a/Lib/test/final_b.py +++ b/Lib/test/final_b.py @@ -3,17 +3,18 @@ """ import shutil +import sys import test.final_a x = 'b' class C: - def __del__(self): + def __del__(self, sys=sys): # Inspect module globals and builtins - print("x =", x) - print("final_a.x =", test.final_a.x) - print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None)) - print("len =", getattr(len, '__name__', None)) + print("x =", x, file=sys.stderr) + print("final_a.x =", test.final_a.x, file=sys.stderr) + print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None), file=sys.stderr) + print("len =", getattr(len, '__name__', None), file=sys.stderr) c = C() _underscored = C() diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index a601a524d6eb72..103ef90359d3f5 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -27,7 +27,7 @@ from types import AsyncGeneratorType, FunctionType from operator import neg from test import support -from test.support import (swap_attr, maybe_get_event_loop_policy) +from test.support import (cpython_only, swap_attr, maybe_get_event_loop_policy) from test.support.os_helper import (EnvironmentVarGuard, TESTFN, unlink) from test.support.script_helper import assert_python_ok from test.support.warnings_helper import check_warnings @@ -2189,11 +2189,11 @@ def test_cleanup(self): import sys class C: - def __del__(self): - print("before") + def __del__(self, sys=sys): + print("before", file=sys.stderr) # Check that builtins still exist len(()) - print("after") + print("after", file=sys.stderr) c = C() # Make this module survive until builtins and sys are cleaned @@ -2211,7 +2211,30 @@ def __del__(self): # implemented in Python rc, out, err = assert_python_ok("-c", code, PYTHONIOENCODING="ascii") - self.assertEqual(["before", "after"], out.decode().splitlines()) + self.assertEqual(["before", "after"], err.decode().splitlines()) + + +@cpython_only +class ImmortalTests(unittest.TestCase): + def test_immortal(self): + none_refcount = sys.getrefcount(None) + true_refcount = sys.getrefcount(True) + false_refcount = sys.getrefcount(False) + smallint_refcount = sys.getrefcount(100) + + # Assert that all of these immortal instances have large ref counts + self.assertGreater(none_refcount, 1e8) + self.assertGreater(true_refcount, 1e8) + self.assertGreater(false_refcount, 1e8) + self.assertGreater(smallint_refcount, 1e8) + + # Confirm that the refcount doesn't change even with a new ref to them + l = [None, True, False, 100] + self.assertEqual(sys.getrefcount(None), none_refcount) + self.assertEqual(sys.getrefcount(True), true_refcount) + self.assertEqual(sys.getrefcount(False), false_refcount) + self.assertEqual(sys.getrefcount(100), smallint_refcount) + class TestType(unittest.TestCase): diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index c4d4355dec9c6d..34142c75450c79 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -714,22 +714,24 @@ def test_gc_main_module_at_shutdown(self): # Create a reference cycle through the __main__ module and check # it gets collected at interpreter shutdown. code = """if 1: + import sys class C: - def __del__(self): - print('__del__ called') + def __del__(self, sys=sys): + print('__del__ called', file=sys.stderr) l = [C()] l.append(l) """ rc, out, err = assert_python_ok('-c', code) - self.assertEqual(out.strip(), b'__del__ called') + self.assertEqual(err.strip(), b'__del__ called') def test_gc_ordinary_module_at_shutdown(self): # Same as above, but with a non-__main__ module. with temp_dir() as script_dir: module = """if 1: + import sys class C: - def __del__(self): - print('__del__ called') + def __del__(self, sys=sys): + print('__del__ called', file=sys.stderr) l = [C()] l.append(l) """ @@ -740,13 +742,14 @@ def __del__(self): """ % (script_dir,) make_script(script_dir, 'gctest', module) rc, out, err = assert_python_ok('-c', code) - self.assertEqual(out.strip(), b'__del__ called') + self.assertEqual(err.strip(), b'') def test_global_del_SystemExit(self): code = """if 1: + import sys class ClassWithDel: - def __del__(self): - print('__del__ called') + def __del__(self, sys=sys): + print('__del__ called', file=sys.stderr) a = ClassWithDel() a.link = a raise SystemExit(0)""" @@ -754,7 +757,7 @@ def __del__(self): with open(TESTFN, 'w', encoding="utf-8") as script: script.write(code) rc, out, err = assert_python_ok(TESTFN) - self.assertEqual(out.strip(), b'__del__ called') + self.assertEqual(err.strip(), b'__del__ called') def test_get_stats(self): stats = gc.get_stats() diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index e9abd153a3e8cf..759c11a5842304 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -3518,6 +3518,7 @@ def _check_create_at_shutdown(self, **kwargs): code = """if 1: import codecs import {iomod} as io + import sys # Avoid looking up codecs at shutdown codecs.lookup('utf-8') @@ -3525,27 +3526,21 @@ def _check_create_at_shutdown(self, **kwargs): class C: def __init__(self): self.buf = io.BytesIO() - def __del__(self): + def __del__(self, sys=sys): io.TextIOWrapper(self.buf, **{kwargs}) - print("ok") + print("ok", file=sys.stderr) c = C() """.format(iomod=iomod, kwargs=kwargs) return assert_python_ok("-c", code) def test_create_at_shutdown_without_encoding(self): rc, out, err = self._check_create_at_shutdown() - if err: - # Can error out with a RuntimeError if the module state - # isn't found. - self.assertIn(self.shutdown_error, err.decode()) - else: - self.assertEqual("ok", out.decode().strip()) + self.assertEqual("ok", err.decode().strip()) def test_create_at_shutdown_with_encoding(self): rc, out, err = self._check_create_at_shutdown(encoding='utf-8', errors='strict') - self.assertFalse(err) - self.assertEqual("ok", out.decode().strip()) + self.assertEqual("ok", err.decode().strip()) def test_read_byteslike(self): r = MemviewBytesIO(b'Just some random string\n') diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py index 619348e0e40c03..1c856c42b07c18 100644 --- a/Lib/test/test_module.py +++ b/Lib/test/test_module.py @@ -269,15 +269,20 @@ def test_module_repr_source(self): def test_module_finalization_at_shutdown(self): # Module globals and builtins should still be available during shutdown rc, out, err = assert_python_ok("-c", "from test import final_a") - self.assertFalse(err) - lines = out.splitlines() + lines = err.splitlines() self.assertEqual(set(lines), { - b"x = a", - b"x = b", - b"final_a.x = a", - b"final_b.x = b", - b"len = len", - b"shutil.rmtree = rmtree"}) + b'x = a', + b'final_b.x = b', + b'x = b', + b'final_a.x = a', + b'shutil.rmtree = rmtree', + b'len = len', + b'x = None', + b'final_b.x = b', + b'x = None', + b'final_a.x = None', + b'shutil.rmtree = rmtree', + b'len = len'}) def test_descriptor_errors_propagate(self): class Descr: diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index babc8a690877a2..abc7b63c9e92ea 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -915,7 +915,7 @@ class RefLeakTest(unittest.TestCase): def test_leak(self): GLOBAL_LIST.append(object()) """) - self.check_leak(code, 'references') + self.check_leak(code, 'memory blocks') @unittest.skipUnless(Py_DEBUG, 'need a debug build') def test_huntrleaks_fd_leak(self): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index f828d1b15d2868..4c65f11f428980 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -385,7 +385,8 @@ def test_refcount(self): self.assertRaises(TypeError, sys.getrefcount) c = sys.getrefcount(None) n = None - self.assertEqual(sys.getrefcount(None), c+1) + # Singleton refcnts don't change + self.assertEqual(sys.getrefcount(None), c) del n self.assertEqual(sys.getrefcount(None), c) if hasattr(sys, "gettotalrefcount"): @@ -977,14 +978,14 @@ def test_issue20602(self): import sys class A: def __del__(self, sys=sys): - print(sys.flags) - print(sys.float_info) + print(sys.flags, file=sys.stderr) + print(sys.float_info, file=sys.stderr) a = A() """ rc, out, err = assert_python_ok('-c', code) - out = out.splitlines() - self.assertIn(b'sys.flags', out[0]) - self.assertIn(b'sys.float_info', out[1]) + err = err.splitlines() + self.assertIn(b'sys.flags', err[0]) + self.assertIn(b'sys.float_info', err[1]) def test_sys_ignores_cleaning_up_user_data(self): code = """if 1: diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 4830571474b5bf..13d4534be90821 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -649,28 +649,29 @@ def test_main_thread_during_shutdown(self): # bpo-31516: current_thread() should still point to the main thread # at shutdown code = """if 1: - import gc, threading + import gc, sys, threading main_thread = threading.current_thread() assert main_thread is threading.main_thread() # sanity check class RefCycle: - def __init__(self): + def __init__(self, main_thread): self.cycle = self + self.main_thread = main_thread - def __del__(self): + def __del__(self, sys=sys): print("GC:", - threading.current_thread() is main_thread, - threading.main_thread() is main_thread, - threading.enumerate() == [main_thread]) + threading.current_thread() is self.main_thread, + threading.main_thread() is self.main_thread, + threading.enumerate() == [self.main_thread], + file=sys.stderr) - RefCycle() + RefCycle(main_thread) gc.collect() # sanity check - x = RefCycle() + x = RefCycle(main_thread) """ _, out, err = assert_python_ok("-c", code) - data = out.decode() - self.assertEqual(err, b"") + data = err.decode() self.assertEqual(data.splitlines(), ["GC: True True True"] * 2) @@ -878,18 +879,19 @@ def test_locals_at_exit(self): # bpo-19466: thread locals must not be deleted before destructors # are called rc, out, err = assert_python_ok("-c", """if 1: + import sys import threading class Atexit: - def __del__(self): - print("thread_dict.atexit = %r" % thread_dict.atexit) + def __del__(self, sys=sys): + print("thread_dict.atexit = %r" % thread_dict.atexit, file=sys.stderr) thread_dict = threading.local() thread_dict.atexit = "value" - atexit = Atexit() + _atexit = Atexit() """) - self.assertEqual(out.rstrip(), b"thread_dict.atexit = 'value'") + self.assertEqual(err.rstrip(), b"thread_dict.atexit = 'value'") def test_boolean_target(self): # bpo-41149: A thread that had a boolean value of False would not diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 4b1b4e193cb165..4f740fd505329a 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1238,17 +1238,16 @@ def test_finalization(self): # during Python finalization code = """ import warnings -warn = warnings.warn class A: def __del__(self): - warn("test") + warnings.warn("test") a=A() """ rc, out, err = assert_python_ok("-c", code) self.assertEqual(err.decode().rstrip(), - ':7: UserWarning: test') + ':6: UserWarning: test') def test_late_resource_warning(self): # Issue #21925: Emitting a ResourceWarning late during the Python diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-12-18-08-57-24.bpo-40255.XDDrSO.rst b/Misc/NEWS.d/next/Core and Builtins/2021-12-18-08-57-24.bpo-40255.XDDrSO.rst new file mode 100644 index 00000000000000..43983d000ee4b0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-12-18-08-57-24.bpo-40255.XDDrSO.rst @@ -0,0 +1,2 @@ +This introduces Immortal Instances which allows objects to bypass reference +counting and remain alive throughout the execution of the runtime diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 802c3eadccfb0c..a0e5ef6d39e876 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -281,8 +281,12 @@ gc_list_move(PyGC_Head *node, PyGC_Head *list) /* Unlink from current list. */ PyGC_Head *from_prev = GC_PREV(node); PyGC_Head *from_next = GC_NEXT(node); - _PyGCHead_SET_NEXT(from_prev, from_next); - _PyGCHead_SET_PREV(from_next, from_prev); + if (from_next) { + _PyGCHead_SET_NEXT(from_prev, from_next); + } + if (from_prev) { + _PyGCHead_SET_PREV(from_next, from_prev); + } /* Relink at end of new list. */ // list must not have flags. So we can skip macros. @@ -1952,6 +1956,95 @@ gc_get_freeze_count_impl(PyObject *module) return gc_list_size(&gcstate->permanent_generation.head); } +static int +immortalize_object(PyObject *obj, PyObject *Py_UNUSED(ignored)) +{ + _Py_SetImmortal(obj); + /* Special case for PyCodeObjects since they don't have a tp_traverse */ + if (PyCode_Check(obj)) { + PyCodeObject *code = (PyCodeObject *)obj; + _Py_SetImmortal(code->co_code); + _Py_SetImmortal(code->co_consts); + _Py_SetImmortal(code->co_names); + _Py_SetImmortal(code->co_varnames); + _Py_SetImmortal(code->co_freevars); + _Py_SetImmortal(code->co_cellvars); + _Py_SetImmortal(code->co_filename); + _Py_SetImmortal(code->co_name); + _Py_SetImmortal(code->co_linetable); + } + return 0; +} + +PyObject * +_PyGC_ImmortalizeHeap(void) { + PyGC_Head *gc, *list; + PyThreadState *tstate = _PyThreadState_GET(); + GCState *gcstate = &tstate->interp->gc; + + /* Remove any dead objects to avoid immortalizing them */ + PyGC_Collect(); + + /* Move all instances into the permanent generation */ + gc_freeze_impl(NULL); + + /* Immortalize all instances in the permanent generation */ + list = &gcstate->permanent_generation.head; + for (gc = GC_NEXT(list); gc != list; gc = GC_NEXT(gc)) { + _Py_SetImmortal(FROM_GC(gc)); + /* This can traverse to non-GC-tracked objects, and some of those + * non-GC-tracked objects (e.g. dicts) can later become GC-tracked, and + * not be in the permanent generation. So it is possible for immortal + * objects to enter GC collection. Currently what happens in that case + * is that their immortal bit makes it look like they have a very large + * refcount, so they are not collected. */ + Py_TYPE(FROM_GC(gc))->tp_traverse( + FROM_GC(gc), (visitproc)immortalize_object, NULL); + } + Py_RETURN_NONE; +} + +static int +transitive_immortalize(PyObject *obj, PyGC_Head *permanent_gen) +{ + if (_Py_IsImmortal(obj)) { + return 0; + } + + _Py_SetImmortal(obj); + /* Special case for PyCodeObjects since they don't have a tp_traverse */ + if (PyCode_Check(obj)) { + PyCodeObject *code = (PyCodeObject *)obj; + _Py_SetImmortal(code->co_code); + _Py_SetImmortal(code->co_consts); + _Py_SetImmortal(code->co_names); + _Py_SetImmortal(code->co_varnames); + _Py_SetImmortal(code->co_freevars); + _Py_SetImmortal(code->co_cellvars); + _Py_SetImmortal(code->co_filename); + _Py_SetImmortal(code->co_name); + _Py_SetImmortal(code->co_linetable); + } + + PyTypeObject* tp = Py_TYPE(obj); + if (tp->tp_traverse) { + gc_list_move(AS_GC(obj), permanent_gen); + tp->tp_traverse(obj, (visitproc)transitive_immortalize, permanent_gen); + } + return 0; +} + +PyObject * +_PyGC_TransitiveImmortalize(PyObject *obj) { + _Py_SetImmortal(obj); + Py_TYPE(obj)->tp_traverse( + obj, + (visitproc)transitive_immortalize, + &_PyThreadState_GET()->interp->gc.permanent_generation.head + ); + Py_RETURN_NONE; +} + PyDoc_STRVAR(gc__doc__, "This module provides access to the garbage collector for reference cycles.\n" diff --git a/Modules/main.c b/Modules/main.c index 2443f5631b94bb..dd6bf101190d42 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -698,6 +698,8 @@ pymain_main(_PyArgv *args) pymain_exit_error(status); } + _PyGC_ImmortalizeHeap(); + return Py_RunMain(); } diff --git a/Objects/longobject.c b/Objects/longobject.c index 3438906d842758..ab6ad2d8dfd768 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -47,9 +47,7 @@ static PyObject * get_small_int(sdigit ival) { assert(IS_SMALL_INT(ival)); - PyObject *v = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + ival]; - Py_INCREF(v); - return v; + return (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + ival]; } static PyLongObject * diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index bd5e5611ec27e0..46237f65ae02d2 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -571,6 +571,104 @@ PyModule_GetState(PyObject* m) return _PyModule_GetState(m); } +void +module_dict_clear_phase_one(PyObject *d) +{ + /* Phase one only clears names starting with a single underscore + * while also excluding modules from being deleted + */ + Py_ssize_t pos; + PyObject *key, *value; + + pos = 0; + int verbose = _Py_GetConfig()->verbose; + while (PyDict_Next(d, &pos, &key, &value)) { + if (value == Py_None || PyModule_Check(value) || !PyUnicode_Check(key)) { + continue; + } + if (PyUnicode_READ_CHAR(key, 0) != '_') { + continue; + } + if (PyUnicode_READ_CHAR(key, 1) == '_') { + continue; + } + if (verbose > 1) { + const char *s = PyUnicode_AsUTF8(key); + if (s != NULL) + PySys_WriteStderr("# clear[1] %s\n", s); + else + PyErr_Clear(); + } + if (PyDict_SetItem(d, key, Py_None) != 0) { + PyErr_WriteUnraisable(NULL); + } + } +} + +void +module_dict_clear_phase_two(PyObject *d) +{ + /* Phase two, clears all names except for __builtins__ and modules */ + Py_ssize_t pos; + PyObject *key, *value; + + + /* First, clear only names starting with a single underscore */ + pos = 0; + int verbose = _Py_GetConfig()->verbose; + while (PyDict_Next(d, &pos, &key, &value)) { + if (value == Py_None || PyModule_Check(value) || !PyUnicode_Check(key)) { + continue; + } + if (PyUnicode_READ_CHAR(key, 0) == '_' || + _PyUnicode_EqualToASCIIString(key, "__builtins__")) + { + continue; + } + if (verbose > 1) { + const char *s = PyUnicode_AsUTF8(key); + if (s != NULL) + PySys_WriteStderr("# clear[1] %s\n", s); + else + PyErr_Clear(); + } + if (PyDict_SetItem(d, key, Py_None) != 0) { + PyErr_WriteUnraisable(NULL); + } + } +} + +void +module_dict_clear_phase_three(PyObject *d) +{ + /* Phase three, clears all modules except __builtins__*/ + Py_ssize_t pos; + PyObject *key, *value; + + + /* First, clear only names starting with a single underscore */ + pos = 0; + int verbose = _Py_GetConfig()->verbose; + while (PyDict_Next(d, &pos, &key, &value)) { + if (value == Py_None || !PyModule_Check(value) || !PyUnicode_Check(key)) { + continue; + } + if (_PyUnicode_EqualToASCIIString(key, "__builtins__")) { + continue; + } + if (verbose > 1) { + const char *s = PyUnicode_AsUTF8(key); + if (s != NULL) + PySys_WriteStderr("# clear[1] %s\n", s); + else + PyErr_Clear(); + } + if (PyDict_SetItem(d, key, Py_None) != 0) { + PyErr_WriteUnraisable(NULL); + } + } +} + void _PyModule_Clear(PyObject *m) { @@ -579,6 +677,24 @@ _PyModule_Clear(PyObject *m) _PyModule_ClearDict(d); } +void +_PyModule_PhasedClear(PyObject *m, int phase) +{ + PyObject *d = ((PyModuleObject *)m)->md_dict; + if (d == NULL) { + return; + } + if (phase == 1) { + module_dict_clear_phase_one(d); + } + if (phase == 2) { + module_dict_clear_phase_two(d); + } + if (phase == 3) { + module_dict_clear_phase_three(d); + } +} + void _PyModule_ClearDict(PyObject *d) { @@ -641,6 +757,7 @@ _PyModule_ClearDict(PyObject *d) } + /*[clinic input] class module "PyModuleObject *" "&PyModule_Type" [clinic start generated code]*/ diff --git a/Objects/object.c b/Objects/object.c index 3044c862fb9dac..c2a5c2ef091cfe 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1705,7 +1705,8 @@ PyTypeObject _PyNone_Type = { PyObject _Py_NoneStruct = { _PyObject_EXTRA_INIT - 1, &_PyNone_Type + _Py_IMMORTAL_REFCNT, + &_PyNone_Type }; /* NotImplemented is an object that can be used to signal that an @@ -1994,7 +1995,9 @@ _Py_NewReference(PyObject *op) #ifdef Py_REF_DEBUG _Py_RefTotal++; #endif - Py_SET_REFCNT(op, 1); + /* Do not use Py_SET_REFCNT to skip the Immortal Instance check. This + * API guarantees that an instance will always be set to a refcnt of 1 */ + op->ob_refcnt = 1; #ifdef Py_TRACE_REFS _Py_AddToAllObjects(op, 1); #endif diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 908ad514925999..df1de0eb1ce016 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1941,26 +1941,6 @@ unicode_dealloc(PyObject *unicode) case SSTATE_NOT_INTERNED: break; - case SSTATE_INTERNED_MORTAL: - { -#ifdef INTERNED_STRINGS - /* Revive the dead object temporarily. PyDict_DelItem() removes two - references (key and value) which were ignored by - PyUnicode_InternInPlace(). Use refcnt=3 rather than refcnt=2 - to prevent calling unicode_dealloc() again. Adjust refcnt after - PyDict_DelItem(). */ - assert(Py_REFCNT(unicode) == 0); - Py_SET_REFCNT(unicode, 3); - if (PyDict_DelItem(interned, unicode) != 0) { - _PyErr_WriteUnraisableMsg("deletion of interned string failed", - NULL); - } - assert(Py_REFCNT(unicode) == 1); - Py_SET_REFCNT(unicode, 0); -#endif - break; - } - case SSTATE_INTERNED_IMMORTAL: _PyObject_ASSERT_FAILED_MSG(unicode, "Immortal interned string died"); break; @@ -15608,16 +15588,12 @@ PyUnicode_InternInPlace(PyObject **p) } if (t != s) { - Py_INCREF(t); Py_SETREF(*p, t); return; } - /* The two references in interned dict (key and value) are not counted by - refcnt. unicode_dealloc() and _PyUnicode_ClearInterned() take care of - this. */ - Py_SET_REFCNT(s, Py_REFCNT(s) - 2); - _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL; + _Py_SetImmortal(s); + _PyUnicode_STATE(*p).interned = SSTATE_INTERNED_IMMORTAL; #else // PyDict expects that interned strings have their hash // (PyASCIIObject.hash) already computed. @@ -15638,10 +15614,6 @@ PyUnicode_InternImmortal(PyObject **p) } PyUnicode_InternInPlace(p); - if (PyUnicode_CHECK_INTERNED(*p) != SSTATE_INTERNED_IMMORTAL) { - _PyUnicode_STATE(*p).interned = SSTATE_INTERNED_IMMORTAL; - Py_INCREF(*p); - } } PyObject * @@ -15668,49 +15640,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp) } assert(PyDict_CheckExact(interned)); - /* Interned unicode strings are not forcibly deallocated; rather, we give - them their stolen references back, and then clear and DECREF the - interned dict. */ - -#ifdef INTERNED_STATS - fprintf(stderr, "releasing %zd interned strings\n", - PyDict_GET_SIZE(interned)); - - Py_ssize_t immortal_size = 0, mortal_size = 0; -#endif - Py_ssize_t pos = 0; - PyObject *s, *ignored_value; - while (PyDict_Next(interned, &pos, &s, &ignored_value)) { - assert(PyUnicode_IS_READY(s)); - - switch (PyUnicode_CHECK_INTERNED(s)) { - case SSTATE_INTERNED_IMMORTAL: - Py_SET_REFCNT(s, Py_REFCNT(s) + 1); -#ifdef INTERNED_STATS - immortal_size += PyUnicode_GET_LENGTH(s); -#endif - break; - case SSTATE_INTERNED_MORTAL: - // Restore the two references (key and value) ignored - // by PyUnicode_InternInPlace(). - Py_SET_REFCNT(s, Py_REFCNT(s) + 2); -#ifdef INTERNED_STATS - mortal_size += PyUnicode_GET_LENGTH(s); -#endif - break; - case SSTATE_NOT_INTERNED: - /* fall through */ - default: - Py_UNREACHABLE(); - } - _PyUnicode_STATE(s).interned = SSTATE_NOT_INTERNED; - } -#ifdef INTERNED_STATS - fprintf(stderr, - "total size of all interned strings: %zd/%zd mortal/immortal\n", - mortal_size, immortal_size); -#endif - + /* Interned unicode strings are not forcibly deallocated */ PyDict_Clear(interned); Py_CLEAR(interned); } diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 3830dc3f8b6ec7..1273020d1bc393 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -1829,7 +1829,7 @@ static int test_unicode_id_init(void) str1 = _PyUnicode_FromId(&PyId_test_unicode_id_init); assert(str1 != NULL); - assert(Py_REFCNT(str1) == 1); + assert(_Py_IsImmortal(str1)); str2 = PyUnicode_FromString("test_unicode_id_init"); assert(str2 != NULL); diff --git a/Python/import.c b/Python/import.c index 74f8e1dd4c30d1..2dabf270e19083 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1829,6 +1829,10 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals, if (mod == NULL) { goto error; } + // Immortalize top level modules + if (tstate->recursion_limit - tstate->recursion_remaining == 1) { + _PyGC_TransitiveImmortalize(mod); + } } has_from = 0; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 4a3a1abb3a4f0a..42406c496fc249 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1362,17 +1362,29 @@ finalize_modules_delete_special(PyThreadState *tstate, int verbose) static PyObject* finalize_remove_modules(PyObject *modules, int verbose) { - PyObject *weaklist = PyList_New(0); - if (weaklist == NULL) { + PyObject *stdlib_weaklist = PyList_New(0); + if (stdlib_weaklist == NULL) { + PyErr_WriteUnraisable(NULL); + } + PyObject *user_weaklist = PyList_New(0); + if (user_weaklist == NULL) { + PyErr_WriteUnraisable(NULL); + } + PyObject *stdlib_list = _PySys_StdlibModuleNameList(); + if (stdlib_list == NULL) { PyErr_WriteUnraisable(NULL); } #define STORE_MODULE_WEAKREF(name, mod) \ - if (weaklist != NULL) { \ + if (stdlib_list != NULL) { \ + PyObject *list = user_weaklist; \ + if (PySequence_Contains(stdlib_list, name)) { \ + list = stdlib_weaklist; \ + } \ PyObject *wr = PyWeakref_NewRef(mod, NULL); \ if (wr) { \ PyObject *tup = PyTuple_Pack(2, name, wr); \ - if (!tup || PyList_Append(weaklist, tup) < 0) { \ + if (!tup || PyList_Append(list, tup) < 0) { \ PyErr_WriteUnraisable(NULL); \ } \ Py_XDECREF(tup); \ @@ -1427,7 +1439,11 @@ finalize_remove_modules(PyObject *modules, int verbose) #undef CLEAR_MODULE #undef STORE_MODULE_WEAKREF - return weaklist; + PyObject *result = PyTuple_Pack(2, stdlib_weaklist, user_weaklist); + if (result == NULL) { + PyErr_WriteUnraisable(NULL); + } + return result; } @@ -1462,28 +1478,35 @@ finalize_restore_builtins(PyThreadState *tstate) static void -finalize_modules_clear_weaklist(PyInterpreterState *interp, +finalize_modules_clear_weaklist(PyThreadState *tstate, + PyInterpreterState *interp, PyObject *weaklist, int verbose) { // First clear modules imported later - for (Py_ssize_t i = PyList_GET_SIZE(weaklist) - 1; i >= 0; i--) { - PyObject *tup = PyList_GET_ITEM(weaklist, i); - PyObject *name = PyTuple_GET_ITEM(tup, 0); - PyObject *mod = PyWeakref_GET_OBJECT(PyTuple_GET_ITEM(tup, 1)); - if (mod == Py_None) { - continue; - } - assert(PyModule_Check(mod)); - PyObject *dict = PyModule_GetDict(mod); - if (dict == interp->builtins || dict == interp->sysdict) { - continue; - } - Py_INCREF(mod); - if (verbose && PyUnicode_Check(name)) { - PySys_FormatStderr("# cleanup[3] wiping %U\n", name); + int max_module_phase = 3; + for (int phase = 1; phase <= max_module_phase; phase++) { + for (Py_ssize_t i = PyList_GET_SIZE(weaklist) - 1; i >= 0; i--) { + PyObject *tup = PyList_GET_ITEM(weaklist, i); + PyObject *name = PyTuple_GET_ITEM(tup, 0); + PyObject *mod = PyWeakref_GET_OBJECT(PyTuple_GET_ITEM(tup, 1)); + if (mod == Py_None) { + continue; + } + assert(PyModule_Check(mod)); + PyObject *dict = PyModule_GetDict(mod); + if (dict == interp->builtins || dict == interp->sysdict) { + continue; + } + Py_INCREF(mod); + if (verbose && PyUnicode_Check(name)) { + PySys_FormatStderr("# cleanup[3] wiping %U\n", name); + } + _PyModule_PhasedClear(mod, phase); + if (max_module_phase) { + Py_DECREF(mod); + } } - _PyModule_Clear(mod); - Py_DECREF(mod); + _PyGC_CollectNoFail(tstate); } } @@ -1529,11 +1552,16 @@ finalize_modules(PyThreadState *tstate) // Remove all modules from sys.modules, hoping that garbage collection // can reclaim most of them: set all sys.modules values to None. // - // We prepare a list which will receive (name, weakref) tuples of - // modules when they are removed from sys.modules. The name is used - // for diagnosis messages (in verbose mode), while the weakref helps - // detect those modules which have been held alive. - PyObject *weaklist = finalize_remove_modules(modules, verbose); + // This prepares two lists, the user defined list of modules as well + // as stdlib list of modules. The user modules will be destroyed first in + // order to guarantee builtin module and type availability within a user + // defined `__del__` during the runtime shutdown. + // + // These lists will receive (name, weakref) tuples of modules when they are + // removed from sys.modules. The name is used for diagnosis messages (in + // verbose mode), while the weakref helps detect those modules which have + // been held alive. + PyObject *weaklist_tuple = finalize_remove_modules(modules, verbose); // Clear the modules dict finalize_clear_modules_dict(modules); @@ -1549,24 +1577,45 @@ finalize_modules(PyThreadState *tstate) // machinery. _PyGC_DumpShutdownStats(interp); - if (weaklist != NULL) { - // Now, if there are any modules left alive, clear their globals to - // minimize potential leaks. All C extension modules actually end - // up here, since they are kept alive in the interpreter state. - // - // The special treatment of "builtins" here is because even - // when it's not referenced as a module, its dictionary is - // referenced by almost every module's __builtins__. Since - // deleting a module clears its dictionary (even if there are - // references left to it), we need to delete the "builtins" - // module last. Likewise, we don't delete sys until the very - // end because it is implicitly referenced (e.g. by print). - // - // Since dict is ordered in CPython 3.6+, modules are saved in - // importing order. First clear modules imported later. - finalize_modules_clear_weaklist(interp, weaklist, verbose); - Py_DECREF(weaklist); + // Finally, clear the module globals to minimize potential leaks as well + // as clearing up the immortal modules. All C extension modules actually + // end up here, since they are kept alive in the interpreter state. + // + // The module cleanup order is as follows: + // User defined modules: + // 1) Globals starting with '_', excluding modules + // 2) Globals, excluding modules and __builtins__ + // 3) Modules and __builtins__ + // Stdlib modules: + // 4) Globals starting with '_', excluding modules + // 5) Globals, excluding modules and __builtins__ + // 6) Modules and __builtins__ + // + // After each of these steps, a full GC collection is triggered in order + // to clear cycles and trigger the execution of `__del__` functions + // while other modules and types are still available to be used. + // + // The special treatment of "builtins" here is because even + // when it's not referenced as a module, its dictionary is + // referenced by almost every module's __builtins__. Since + // deleting a module clears its dictionary (even if there are + // references left to it), we need to delete the "builtins" + // module last. Likewise, we don't delete sys until the very + // end because it is implicitly referenced (e.g. by print). + if (weaklist_tuple != NULL) { + PyObject *user_weaklist = PyTuple_GET_ITEM(weaklist_tuple, 1); + if (user_weaklist != NULL) { + finalize_modules_clear_weaklist(tstate, interp, user_weaklist, verbose); + } + Py_XDECREF(user_weaklist); + + PyObject *stdlib_weaklist = PyTuple_GET_ITEM(weaklist_tuple, 0); + if (stdlib_weaklist != NULL) { + finalize_modules_clear_weaklist(tstate, interp, stdlib_weaklist, verbose); + } + Py_XDECREF(stdlib_weaklist); } + Py_XDECREF(weaklist_tuple); // Clear sys and builtins modules dict finalize_clear_sys_builtins_dict(interp, verbose); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 57bf04dd306aee..288b5d102e112f 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2074,8 +2074,8 @@ list_builtin_module_names(void) } -static PyObject * -list_stdlib_module_names(void) +PyObject * +_PySys_StdlibModuleNameList(void) { Py_ssize_t len = Py_ARRAY_LENGTH(_Py_stdlib_module_names); PyObject *names = PyTuple_New(len); @@ -2810,7 +2810,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) SET_SYS("hash_info", get_hash_info(tstate)); SET_SYS("maxunicode", PyLong_FromLong(0x10FFFF)); SET_SYS("builtin_module_names", list_builtin_module_names()); - SET_SYS("stdlib_module_names", list_stdlib_module_names()); + SET_SYS("stdlib_module_names", _PySys_StdlibModuleNameList()); #if PY_BIG_ENDIAN SET_SYS_FROM_STRING("byteorder", "big"); #else