Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34.1k
gh-84436: Implement Immortal Objects#19474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from1 commit
0c930b77005944c6a1bfa51e4879cc2ece372d12faf04776efa8d668f06663336e0a9a2f9fa29a1bc9810b12a161332f0317883d7bc23d4d825f8fa9a6f4f96899f5cd285010b7d2c21093edae54af788b29c8ff701579aa12da60a99a51715ad069874248a357a084937d4f6d3bcea6b439b7bea23b5a07fa8406ff9ab23d6f70966dc3e4385e075b0bb9957e264650c2bc92ce1319afbb1b1261f128ae6bf2e262ff2c708205203866ad765c7e30e530f0b3375cd6570aff4e49b58aef478b17225f3e0f6ad8e54c8fe2e274104c8292575949df42baee89f7da0f84bea515dfb5863f7287b25af016702cf9af25fd52abe8695538a14a9c828369ee41af6f835e6d8573af4ad19ff666c625f1379d501c9ee6d287b57cc736a7c60f07601321ff6a719b419f3ed398f72afe52d6d789fd8a983478467eb5da8cdef8da3fe6727e38df3ce73f6dcdd68efa118cff33168a85c9ada9fd5d3beb9ea342e3d78a56016d59e3f49c13c8262e5696c7caa3493c850f38657401a3c36bd2d949df144715f7365ea9f01cc39b6173ae8374ba7cfe17a2912388ede6734bdf3cab1f6e4c2c228e219ebdccd42e1699e7549d7df47300238eb9355ca2eedd412ccf8b61e57910d6437df7ba75726418b2ff1468f52e30fea45aa8c34d74a4c59be58d4f00f7f8747039d01017e16f0cf327997d57749680ec71c742bc28cb0c8b694f6abab4d1dfe27a766154159513a760329b5a5e29d5f88cbb67efa7608ebb3db2c3d2425684be74529e23cfb56b6a748e80033c86d520fbc390e0016bc726b05e0cd08f7fbf0192fbf961c390cc030016a093c4056c0fdba74b6e7b433d1e3069da16d22a4bfe04ef7e3b3b142ab3f9513e55a32ff69be7e6e459ce19f50a7c233b09b2b49c018be4cf4aa5b41d2ee06c6a14f247819f69423c61a4a9067181aedd0a1846867b1c57e82b165d85d9d39053b22a9caa2d56f1d81bae61959cb2c21File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
- Loading branch information
Uh oh!
There was an error while loading.Please reload this page.
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -124,12 +124,45 @@ typedef struct { | ||
| #define Py_TYPE(ob) (_PyObject_CAST(ob)->ob_type) | ||
| #define Py_SIZE(ob) (_PyVarObject_CAST(ob)->ob_size) | ||
| /* [RFC] Should we enable Immortal Instances by Default? */ | ||
| #define Py_IMMORTAL_INSTANCES | ||
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| /* Immortalizing causes the instance to not participate in reference counting. | ||
| * Thus, an immortal object will be kept alive until the runtime finalization. | ||
| * This avoids an unnecessary copy-on-write for applications that share | ||
| * a common python heap across many processes. */ | ||
| #ifdef Py_IMMORTAL_INSTANCES | ||
| /* The GC bit-shifts refcounts left by two, and after that shift we still | ||
| * need this to be >> 0, so leave three high zero bits (the sign bit and | ||
| * room for a shift of two.) */ | ||
| static const Py_ssize_t kImmortalBit = 1L << (8 * sizeof(Py_ssize_t) - 4); | ||
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| static inline int _Py_IsImmortal(PyObject *op) | ||
| { | ||
| return (op->ob_refcnt & kImmortalBit) != 0; | ||
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| } | ||
| static inline void _Py_SetImmortal(PyObject *op) | ||
| { | ||
| op->ob_refcnt = kImmortalBit; | ||
| } | ||
| #endif /* Py_IMMORTAL_INSTANCES */ | ||
| static inline int _Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { | ||
| return ob->ob_type == type; | ||
| } | ||
| #define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST_CONST(ob), type) | ||
| static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { | ||
| #ifdef Py_IMMORTAL_INSTANCES | ||
| if (_Py_IsImmortal(ob)) { | ||
eduardo-elizondo marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| return; | ||
| } | ||
eduardo-elizondo marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| #endif /* Py_IMMORTAL_INSTANCES */ | ||
| ob->ob_refcnt = refcnt; | ||
| } | ||
| #define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) | ||
| @@ -358,7 +391,6 @@ given type object has a specified feature. | ||
| /* Type structure has tp_finalize member (3.4) */ | ||
| #define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0) | ||
| /* | ||
| The macros Py_INCREF(op) and Py_DECREF(op) are used to increment or decrement | ||
| reference counts. Py_DECREF calls the object's deallocator function when | ||
| @@ -397,6 +429,11 @@ PyAPI_FUNC(void) _Py_Dealloc(PyObject *); | ||
| static inline void _Py_INCREF(PyObject *op) | ||
| { | ||
| #ifdef Py_IMMORTAL_INSTANCES | ||
| if (_Py_IsImmortal(op)) { | ||
| return; | ||
| } | ||
| #endif /* Py_IMMORTAL_INSTANCES */ | ||
| #ifdef Py_REF_DEBUG | ||
| _Py_RefTotal++; | ||
| #endif | ||
| @@ -411,6 +448,11 @@ static inline void _Py_DECREF( | ||
| #endif | ||
| PyObject *op) | ||
| { | ||
| #ifdef Py_IMMORTAL_INSTANCES | ||
| if (_Py_IsImmortal(op)) { | ||
| return; | ||
| } | ||
| #endif /* Py_IMMORTAL_INSTANCES */ | ||
| #ifdef Py_REF_DEBUG | ||
| _Py_RefTotal--; | ||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1039,6 +1039,46 @@ class Z: | ||
| gc.enable() | ||
| # These tests need to be run in a separate process since gc.immortalize_heap | ||
| # will mess up with the reference count of other tests | ||
| class GCImmortalizeTests(unittest.TestCase): | ||
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page.
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| def test_not_immortal(self): | ||
| obj = [] | ||
| self.assertFalse(gc.is_immortal(obj)) | ||
| def test_is_immortal(self): | ||
| code = """if 1: | ||
| import gc | ||
| obj = [] | ||
| gc.immortalize_heap() | ||
| print(gc.is_immortal(obj)) | ||
| """ | ||
| rc, out, err = assert_python_ok('-c', code) | ||
| self.assertEqual(out.strip(), b'True') | ||
| def test_post_immortalize(self): | ||
| code = """if 1: | ||
| import gc | ||
| gc.immortalize_heap() | ||
| obj = [] | ||
| print(gc.is_immortal(obj)) | ||
| """ | ||
| rc, out, err = assert_python_ok('-c', code) | ||
| self.assertEqual(out.strip(), b'False') | ||
| def test_become_tracked_after_immortalize(self): | ||
| code = """if 1: | ||
| import gc | ||
| d = {} # untracked by gc | ||
| gc.immortalize_heap() | ||
| d["foo"] = [] # now becomes gc-tracked | ||
| gc.collect() # gc should not collect immortal objects | ||
| print(len(d)) | ||
| """ | ||
| rc, out, err = assert_python_ok('-c', code) | ||
| self.assertEqual(out.strip(), b'1') | ||
| class GCCallbackTests(unittest.TestCase): | ||
| def setUp(self): | ||
| # Save gc state and disable it. | ||
| @@ -1369,7 +1409,8 @@ def test_main(): | ||
| try: | ||
| gc.collect() # Delete 2nd generation garbage | ||
| run_unittest( | ||
| GCTests, GCTogglingTests, GCCallbackTests, GCImmortalizeTests) | ||
| finally: | ||
| gc.set_debug(debug) | ||
| # test gc.enable() even if GC is disabled by default | ||
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1939,6 +1939,79 @@ gc_get_freeze_count_impl(PyObject *module) | ||
| return gc_list_size(&gcstate->permanent_generation.head); | ||
| } | ||
| /*[clinic input] | ||
| gc.is_immortal -> bool | ||
| instance: object | ||
| The instance to perform the immortal check on. | ||
| Check if an object has been immortalized. | ||
| [clinic start generated code]*/ | ||
| static int | ||
| gc_is_immortal_impl(PyObject *module, PyObject *instance) | ||
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| /*[clinic end generated code: output=743a163cb6aaf384 input=815182ca5c5d81e4]*/ | ||
| { | ||
| return _Py_IsImmortal(instance); | ||
| } | ||
| 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 */ | ||
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| 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_lnotab); | ||
| } | ||
| return 0; | ||
| } | ||
| /*[clinic input] | ||
| gc.immortalize_heap | ||
| Immortalize all instances accessible through the GC roots. | ||
| [clinic start generated code]*/ | ||
| static PyObject * | ||
| gc_immortalize_heap_impl(PyObject *module) | ||
| /*[clinic end generated code: output=a7bb85fe2e27e4ae input=ca1709e4667c0623]*/ | ||
| { | ||
| PyGC_Head *gc, *list; | ||
| PyThreadState *tstate = _PyThreadState_GET(); | ||
| GCState *gcstate = &tstate->interp->gc; | ||
| /* Remove any dead objects to avoid immortalizing them */ | ||
| PyGC_Collect(); | ||
eduardo-elizondo marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| /* Move all instances into the permanent generation */ | ||
| gc_freeze_impl(module); | ||
| /* 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; | ||
| } | ||
| PyDoc_STRVAR(gc__doc__, | ||
| "This module provides access to the garbage collector for reference cycles.\n" | ||
| @@ -1958,6 +2031,10 @@ PyDoc_STRVAR(gc__doc__, | ||
| "is_finalized() -- Returns true if a given object has been already finalized.\n" | ||
| "get_referrers() -- Return the list of objects that refer to an object.\n" | ||
| "get_referents() -- Return the list of objects that an object refers to.\n" | ||
| #ifdef Py_IMMORTAL_INSTANCES | ||
| "immortalize_heap() -- Immortalize all instances accessible through the GC roots.\n" | ||
| "is_immortal() -- Check if an object has been immortalized.\n" | ||
| #endif | ||
| "freeze() -- Freeze all tracked objects and ignore them for future collections.\n" | ||
| "unfreeze() -- Unfreeze all objects in the permanent generation.\n" | ||
| "get_freeze_count() -- Return the number of objects in the permanent generation.\n"); | ||
| @@ -1983,6 +2060,10 @@ static PyMethodDef GcMethods[] = { | ||
| GC_FREEZE_METHODDEF | ||
| GC_UNFREEZE_METHODDEF | ||
| GC_GET_FREEZE_COUNT_METHODDEF | ||
| #ifdef Py_IMMORTAL_INSTANCES | ||
| GC_IMMORTALIZE_HEAP_METHODDEF | ||
| GC_IS_IMMORTAL_METHODDEF | ||
| #endif | ||
| {NULL, NULL} /* Sentinel */ | ||
| }; | ||