From f505a5956319a8cad682421cfe12a7dd61695e80 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sun, 17 Feb 2019 20:21:35 +0000 Subject: [PATCH 1/9] bpo-36016: Allow gc.getobjects to return the objects in a specific generation --- Doc/library/gc.rst | 5 ++++- Lib/test/test_gc.py | 16 ++++++++++++++ Modules/clinic/gcmodule.c.h | 32 +++++++++++++++++++++------ Modules/gcmodule.c | 44 ++++++++++++++++++++++++++++++++----- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst index 722a0e804314b4..42ffc297320a2b 100644 --- a/Doc/library/gc.rst +++ b/Doc/library/gc.rst @@ -63,11 +63,14 @@ The :mod:`gc` module provides the following functions: Return the debugging flags currently set. -.. function:: get_objects() +.. function:: get_objects(generation=None) Returns a list of all objects tracked by the collector, excluding the list returned. + .. versionchanged:: 3.8 + If generation is not None, return only the objects tracked by the + collector that are in that generation. .. function:: get_stats() diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 16b22422528fb7..b099a7f9a9d727 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -766,6 +766,22 @@ def test_freeze(self): gc.unfreeze() self.assertEqual(gc.get_freeze_count(), 0) + def test_get_objects(self): + gc.collect() + l = [] + l.append(l) + self.assertIn(l, gc.get_objects(generation=0)) + gc.collect(generation=0) + self.assertIn(l, gc.get_objects(generation=1)) + gc.collect(generation=1) + self.assertIn(l, gc.get_objects(generation=2)) + gc.collect(generation=2) + self.assertIn(l, gc.get_objects(generation=2)) + + self.assertEqual(gc.collect(), 0) + del l + self.assertEqual(gc.collect(), 1) + class GCCallbackTests(unittest.TestCase): def setUp(self): diff --git a/Modules/clinic/gcmodule.c.h b/Modules/clinic/gcmodule.c.h index 4fd2ea0720ac27..5df04ec824185c 100644 --- a/Modules/clinic/gcmodule.c.h +++ b/Modules/clinic/gcmodule.c.h @@ -216,21 +216,39 @@ gc_get_count(PyObject *module, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(gc_get_objects__doc__, -"get_objects($module, /)\n" +"get_objects($module, /, generation=-1)\n" "--\n" "\n" -"Return a list of objects tracked by the collector (excluding the list returned)."); +"Return a list of objects tracked by the collector (excluding the list returned).\n" +"\n" +" generation\n" +" Generation to extract the objects from.\n" +"\n" +"If generation is not None, return only the objects tracked by the collector\n" +"that are in that generation."); #define GC_GET_OBJECTS_METHODDEF \ - {"get_objects", (PyCFunction)gc_get_objects, METH_NOARGS, gc_get_objects__doc__}, + {"get_objects", (PyCFunction)(void(*)(void))gc_get_objects, METH_FASTCALL|METH_KEYWORDS, gc_get_objects__doc__}, static PyObject * -gc_get_objects_impl(PyObject *module); +gc_get_objects_impl(PyObject *module, Py_ssize_t generation); static PyObject * -gc_get_objects(PyObject *module, PyObject *Py_UNUSED(ignored)) +gc_get_objects(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return gc_get_objects_impl(module); + PyObject *return_value = NULL; + static const char * const _keywords[] = {"generation", NULL}; + static _PyArg_Parser _parser = {"|O&:get_objects", _keywords, 0}; + Py_ssize_t generation = -1; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + _Py_convert_optional_to_ssize_t, &generation)) { + goto exit; + } + return_value = gc_get_objects_impl(module, generation); + +exit: + return return_value; } PyDoc_STRVAR(gc_get_stats__doc__, @@ -331,4 +349,4 @@ gc_get_freeze_count(PyObject *module, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=5aa5fdc259503d5f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ff74593fc54c8168 input=a9049054013a1b77]*/ diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 64140c1b8899e2..af37c020dd2332 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1502,27 +1502,61 @@ gc_get_referents(PyObject *self, PyObject *args) /*[clinic input] gc.get_objects + generation: Py_ssize_t(accept={int, NoneType}) = -1 + Generation to extract the objects from. Return a list of objects tracked by the collector (excluding the list returned). + +If generation is not None, return only the objects tracked by the collector +that are in that generation. [clinic start generated code]*/ static PyObject * -gc_get_objects_impl(PyObject *module) -/*[clinic end generated code: output=fcb95d2e23e1f750 input=9439fe8170bf35d8]*/ +gc_get_objects_impl(PyObject *module, Py_ssize_t generation) +/*[clinic end generated code: output=48b35fea4ba6cb0e input=39405560350d35dc]*/ { int i; PyObject* result; result = PyList_New(0); - if (result == NULL) + if (result == NULL) { return NULL; + } + + /* If generation is passed, we extract only that generation */ + if (generation != -1){ + if (generation >= NUM_GENERATIONS) { + PyErr_Format(PyExc_ValueError, + "generation parameter must be less than the number of " + "available generations (%i)", + NUM_GENERATIONS); + goto error; + } + + if (generation < 0) { + PyErr_SetString(PyExc_ValueError, + "generation parameter cannot be negative"); + goto error; + } + + if (append_objects(result, GEN_HEAD(generation))) { + goto error; + } + + return result; + } + + /* If generation is not passed, get all objects from all generations */ for (i = 0; i < NUM_GENERATIONS; i++) { if (append_objects(result, GEN_HEAD(i))) { - Py_DECREF(result); - return NULL; + goto error; } } return result; + +error: + Py_DECREF(result); + return NULL; } /*[clinic input] From a38ce3e2e5d48cc86c0b39a928e2466e5b106a19 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sun, 17 Feb 2019 20:26:34 +0000 Subject: [PATCH 2/9] Add News entry --- Doc/whatsnew/3.8.rst | 7 +++++++ .../2019-02-17-20-23-54.bpo-36016.5Hns-f.rst | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-02-17-20-23-54.bpo-36016.5Hns-f.rst diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 6bc131419ca87c..99d85a79c2a7c8 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -163,6 +163,13 @@ gettext Added :func:`~gettext.pgettext` and its variants. (Contributed by Franz Glasner, Éric Araujo, and Cheryl Sabella in :issue:`2504`.) + +gc +-- +:func:`~gc.get_objects` can now receive an optional parameter indicating a +generation to get objects from. Contributed in :issue:`36016` by Pablo Galindo. + + gzip ---- diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-02-17-20-23-54.bpo-36016.5Hns-f.rst b/Misc/NEWS.d/next/Core and Builtins/2019-02-17-20-23-54.bpo-36016.5Hns-f.rst new file mode 100644 index 00000000000000..078be94a9d81b0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-02-17-20-23-54.bpo-36016.5Hns-f.rst @@ -0,0 +1,2 @@ +``gc.get_objects`` can now receive an optional parameter indicating a +generation to get objects from. Patch by Pablo Galindo. From 604d33128ce11afa67e0f8562b8a04a02ac025a7 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sun, 17 Feb 2019 23:18:40 +0000 Subject: [PATCH 3/9] Check that generations are correctly filtered --- Lib/test/test_gc.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index b099a7f9a9d727..ba1523054d2085 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -771,11 +771,19 @@ def test_get_objects(self): l = [] l.append(l) self.assertIn(l, gc.get_objects(generation=0)) + self.assertNotIn(l, gc.get_objects(generation=1)) + self.assertNotIn(l, gc.get_objects(generation=2)) gc.collect(generation=0) + self.assertNotIn(l, gc.get_objects(generation=0)) self.assertIn(l, gc.get_objects(generation=1)) + self.assertNotIn(l, gc.get_objects(generation=2)) gc.collect(generation=1) + self.assertNotIn(l, gc.get_objects(generation=0)) + self.assertNotIn(l, gc.get_objects(generation=1)) self.assertIn(l, gc.get_objects(generation=2)) gc.collect(generation=2) + self.assertNotIn(l, gc.get_objects(generation=0)) + self.assertNotIn(l, gc.get_objects(generation=1)) self.assertIn(l, gc.get_objects(generation=2)) self.assertEqual(gc.collect(), 0) From 1294e7d4aa2d477c1cd385358f395eb27a868826 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 18 Feb 2019 12:17:58 +0000 Subject: [PATCH 4/9] fixup! Check that generations are correctly filtered --- Doc/library/gc.rst | 8 ++++---- Modules/clinic/gcmodule.c.h | 8 ++++---- Modules/gcmodule.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst index 42ffc297320a2b..93622e2f9f8687 100644 --- a/Doc/library/gc.rst +++ b/Doc/library/gc.rst @@ -66,11 +66,11 @@ The :mod:`gc` module provides the following functions: .. function:: get_objects(generation=None) Returns a list of all objects tracked by the collector, excluding the list - returned. + returned. If *generation* is not None, return only the objects tracked by + the collector that are in that generation. - .. versionchanged:: 3.8 - If generation is not None, return only the objects tracked by the - collector that are in that generation. + .. versionadded:: 3.8 + The optional *generation* parameter is added. .. function:: get_stats() diff --git a/Modules/clinic/gcmodule.c.h b/Modules/clinic/gcmodule.c.h index 5df04ec824185c..f8747e275a5829 100644 --- a/Modules/clinic/gcmodule.c.h +++ b/Modules/clinic/gcmodule.c.h @@ -216,7 +216,7 @@ gc_get_count(PyObject *module, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(gc_get_objects__doc__, -"get_objects($module, /, generation=-1)\n" +"get_objects($module, /, generation=None)\n" "--\n" "\n" "Return a list of objects tracked by the collector (excluding the list returned).\n" @@ -238,11 +238,11 @@ gc_get_objects(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje { PyObject *return_value = NULL; static const char * const _keywords[] = {"generation", NULL}; - static _PyArg_Parser _parser = {"|O&:get_objects", _keywords, 0}; + static _PyArg_Parser _parser = {"|n:get_objects", _keywords, 0}; Py_ssize_t generation = -1; if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - _Py_convert_optional_to_ssize_t, &generation)) { + &generation)) { goto exit; } return_value = gc_get_objects_impl(module, generation); @@ -349,4 +349,4 @@ gc_get_freeze_count(PyObject *module, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=ff74593fc54c8168 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=65e0cb98089d2505 input=a9049054013a1b77]*/ diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index af37c020dd2332..f7eeb08ccc1504 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1502,7 +1502,7 @@ gc_get_referents(PyObject *self, PyObject *args) /*[clinic input] gc.get_objects - generation: Py_ssize_t(accept={int, NoneType}) = -1 + generation: Py_ssize_t(c_default="-1") = None Generation to extract the objects from. Return a list of objects tracked by the collector (excluding the list returned). @@ -1513,7 +1513,7 @@ that are in that generation. static PyObject * gc_get_objects_impl(PyObject *module, Py_ssize_t generation) -/*[clinic end generated code: output=48b35fea4ba6cb0e input=39405560350d35dc]*/ +/*[clinic end generated code: output=48b35fea4ba6cb0e input=726f9bb9b3874c7d]*/ { int i; PyObject* result; From 4d8668d57a7fbdbbf4a84fde888c191e54fbee5a Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 18 Feb 2019 13:30:10 +0000 Subject: [PATCH 5/9] fixup! fixup! Check that generations are correctly filtered --- Doc/library/gc.rst | 4 ++-- Doc/whatsnew/3.8.rst | 5 +++-- Lib/test/test_gc.py | 10 ++++++++++ Modules/clinic/gcmodule.c.h | 6 +++--- Modules/gcmodule.c | 4 ++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst index 93622e2f9f8687..084cd6ac257ebe 100644 --- a/Doc/library/gc.rst +++ b/Doc/library/gc.rst @@ -69,8 +69,8 @@ The :mod:`gc` module provides the following functions: returned. If *generation* is not None, return only the objects tracked by the collector that are in that generation. - .. versionadded:: 3.8 - The optional *generation* parameter is added. + .. versionchanged:: 3.8 + New *generation* parameter. .. function:: get_stats() diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 99d85a79c2a7c8..72d37e8a06d5f1 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -166,8 +166,9 @@ Added :func:`~gettext.pgettext` and its variants. gc -- -:func:`~gc.get_objects` can now receive an optional parameter indicating a -generation to get objects from. Contributed in :issue:`36016` by Pablo Galindo. +:func:`~gc.get_objects` can now receive an optional *generation* parameter +indicating a generation to get objects from. Contributed in +:issue:`36016` by Pablo Galindo. gzip diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index ba1523054d2085..1ea3871fbb6d9d 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -790,6 +790,16 @@ def test_get_objects(self): del l self.assertEqual(gc.collect(), 1) + # Check argument values + gc.collect() + self.assertEqual(len(gc.get_objects()), + len(gc.get_objects(generation=None))) + + self.assertRaises(ValueError, gc.get_objects, 1000) + self.assertRaises(ValueError, gc.get_objects, -1000) + self.assertRaises(TypeError, gc.get_objects, "1") + self.assertRaises(TypeError, gc.get_objects, 1.234) + class GCCallbackTests(unittest.TestCase): def setUp(self): diff --git a/Modules/clinic/gcmodule.c.h b/Modules/clinic/gcmodule.c.h index f8747e275a5829..eece04597b10a8 100644 --- a/Modules/clinic/gcmodule.c.h +++ b/Modules/clinic/gcmodule.c.h @@ -238,11 +238,11 @@ gc_get_objects(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje { PyObject *return_value = NULL; static const char * const _keywords[] = {"generation", NULL}; - static _PyArg_Parser _parser = {"|n:get_objects", _keywords, 0}; + static _PyArg_Parser _parser = {"|O&:get_objects", _keywords, 0}; Py_ssize_t generation = -1; if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &generation)) { + _Py_convert_optional_to_ssize_t, &generation)) { goto exit; } return_value = gc_get_objects_impl(module, generation); @@ -349,4 +349,4 @@ gc_get_freeze_count(PyObject *module, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=65e0cb98089d2505 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=d692bf475f0bb096 input=a9049054013a1b77]*/ diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index f7eeb08ccc1504..c733b89b3580b8 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1502,7 +1502,7 @@ gc_get_referents(PyObject *self, PyObject *args) /*[clinic input] gc.get_objects - generation: Py_ssize_t(c_default="-1") = None + generation: Py_ssize_t(accept={int, NoneType}, c_default="-1") = None Generation to extract the objects from. Return a list of objects tracked by the collector (excluding the list returned). @@ -1513,7 +1513,7 @@ that are in that generation. static PyObject * gc_get_objects_impl(PyObject *module, Py_ssize_t generation) -/*[clinic end generated code: output=48b35fea4ba6cb0e input=726f9bb9b3874c7d]*/ +/*[clinic end generated code: output=48b35fea4ba6cb0e input=ef7da9df9806754c]*/ { int i; PyObject* result; From 6c515b5fada11b89b4a56cada754feff052017fa Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 18 Feb 2019 13:33:15 +0000 Subject: [PATCH 6/9] Address Antoine's feedback --- Lib/test/test_gc.py | 3 --- Modules/gcmodule.c | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 1ea3871fbb6d9d..ede6411dc02881 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -785,10 +785,7 @@ def test_get_objects(self): self.assertNotIn(l, gc.get_objects(generation=0)) self.assertNotIn(l, gc.get_objects(generation=1)) self.assertIn(l, gc.get_objects(generation=2)) - - self.assertEqual(gc.collect(), 0) del l - self.assertEqual(gc.collect(), 1) # Check argument values gc.collect() diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index c733b89b3580b8..045f422c2fa712 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1529,7 +1529,7 @@ gc_get_objects_impl(PyObject *module, Py_ssize_t generation) PyErr_Format(PyExc_ValueError, "generation parameter must be less than the number of " "available generations (%i)", - NUM_GENERATIONS); + NUM_GENERATIONS); goto error; } From 73a0a993885947346c4768f17b587da57c6d025e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 18 Feb 2019 23:38:43 +0000 Subject: [PATCH 7/9] Update Modules/gcmodule.c Co-Authored-By: pablogsal --- Modules/gcmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 045f422c2fa712..31432ab7ca7196 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1524,7 +1524,7 @@ gc_get_objects_impl(PyObject *module, Py_ssize_t generation) } /* If generation is passed, we extract only that generation */ - if (generation != -1){ + if (generation != -1) { if (generation >= NUM_GENERATIONS) { PyErr_Format(PyExc_ValueError, "generation parameter must be less than the number of " From 19912492392debead3c805e25da85781402da3ef Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 18 Feb 2019 23:38:51 +0000 Subject: [PATCH 8/9] Update Modules/gcmodule.c Co-Authored-By: pablogsal --- Modules/gcmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 31432ab7ca7196..fad1356d6b443d 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1546,7 +1546,7 @@ gc_get_objects_impl(PyObject *module, Py_ssize_t generation) return result; } - /* If generation is not passed, get all objects from all generations */ + /* If generation is not passed or None, get all objects from all generations */ for (i = 0; i < NUM_GENERATIONS; i++) { if (append_objects(result, GEN_HEAD(i))) { goto error; From 5b6201435d670b6b7280bb01def9daa297965a98 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Mon, 18 Feb 2019 23:44:04 +0000 Subject: [PATCH 9/9] Adress Victor's feedback --- Doc/whatsnew/3.8.rst | 1 + Lib/test/test_gc.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 72d37e8a06d5f1..1e6196e3b3a94c 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -166,6 +166,7 @@ Added :func:`~gettext.pgettext` and its variants. gc -- + :func:`~gc.get_objects` can now receive an optional *generation* parameter indicating a generation to get objects from. Contributed in :issue:`36016` by Pablo Galindo. diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index ede6411dc02881..65e74d4759ce3a 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -786,8 +786,9 @@ def test_get_objects(self): self.assertNotIn(l, gc.get_objects(generation=1)) self.assertIn(l, gc.get_objects(generation=2)) del l + gc.collect() - # Check argument values + def test_get_objects_arguments(self): gc.collect() self.assertEqual(len(gc.get_objects()), len(gc.get_objects(generation=None)))