Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ struct _gc_runtime_state {
collections, and are awaiting to undergo a full collection for
the first time. */
Py_ssize_t long_lived_pending;
struct gc_generation permanent_generation;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put this field after generation0.

};

PyAPI_FUNC(void) _PyGC_Initialize(struct _gc_runtime_state *);
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,10 @@ def test_get_stats(self):
self.assertEqual(new[1]["collections"], old[1]["collections"])
self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)

def test_freeze(self):
gc.freeze()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should really undo the freezing after this test. We don't want tests to have so large side effects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! This will require adding the "thaw" or "unfreeze" function that will move objects back from the permanent generation to a properly collected one. Do you think it would be enough for that call to move all of them to gen0? This won't be exactly "undoing the freeze" but doesn't require keeping track of which object belonged to which generation before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything that empties the permanent generation is good enough IMHO. People can call gc.collect explicitly afterwards if they want to collect those objects.

PS: as a non-native English locutor, I much prefer "unfreeze" to "thaw" :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, didn't notice your "general comment" until now. So it seems we agree that moving everything to a single generation makes sense. But you're suggesting the oldest generation (gen2) instead. Makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I was just too lazy to add it but fine will update..

self.assertGreater(gc.get_freeze_stats(), 0)


class GCCallbackTests(unittest.TestCase):
def setUp(self):
Expand Down
42 changes: 41 additions & 1 deletion Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ _PyGC_Initialize(struct _gc_runtime_state *state)
state->generations[i] = generations[i];
};
state->generation0 = GEN_HEAD(0);
struct gc_generation permanent_generation = {
{{&permanent_generation.head, &permanent_generation.head, 0}}, 0, 0
};
state->permanent_generation = permanent_generation;
}

/*--------------------------------------------------------------------------
Expand Down Expand Up @@ -813,6 +817,8 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
for (i = 0; i < NUM_GENERATIONS; i++)
PySys_FormatStderr(" %zd",
gc_list_size(GEN_HEAD(i)));
PySys_WriteStderr("\ngc: objects in permanent generation: %d",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%zd

gc_list_size(&_PyRuntime.gc.permanent_generation.head));
t1 = _PyTime_GetMonotonicClock();

PySys_WriteStderr("\n");
Expand Down Expand Up @@ -1405,6 +1411,36 @@ gc_is_tracked(PyObject *module, PyObject *obj)
return result;
}

PyDoc_STRVAR(gc_freeze__doc__,
"freeze() -> None\n"
"\n"
"Freeze all current tracked objects and ignore them for future collections.\n"
"This can be used before a fork to make the gc copy-on-write friendly.\n"
"Note: collection before a fork may free pages for future allocation\n"
"which can cause copy-on-write.\n"
);

static PyObject *
gc_freeze(PyObject *module)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd prefer if you used ArgumentClinic for those functions. It's very easy to use: https://docs.python.org/3/howto/clinic.html

{
for (int i = 0; i < NUM_GENERATIONS; ++i) {
gc_list_merge(GEN_HEAD(i), &_PyRuntime.gc.permanent_generation.head);
_PyRuntime.gc.generations[i].count = 0;
}
Py_RETURN_NONE;
}

PyDoc_STRVAR(gc_get_freeze_stats__doc__,
"get_freeze_stats() -> n\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not equivalent to what get_stats() is returning, can you rename to get_frozen_count()?

"\n"
"Return the number of objects in permanent generations.\n"
);

static PyObject *
gc_get_freeze_stats(PyObject *module) {
return Py_BuildValue("i", gc_list_size(&_PyRuntime.gc.permanent_generation.head));
}


PyDoc_STRVAR(gc__doc__,
"This module provides access to the garbage collector for reference cycles.\n"
Expand All @@ -1422,7 +1458,9 @@ PyDoc_STRVAR(gc__doc__,
"get_objects() -- Return a list of all objects tracked by the collector.\n"
"is_tracked() -- Returns true if a given object is tracked.\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");
"get_referents() -- Return the list of objects that an object refers to.\n"
"freeze() -- Freeze all tracked objects and ignore them for future collections.\n"
"get_freeze_stats() -- Return the number of objects in the permanent generation.\n");

static PyMethodDef GcMethods[] = {
GC_ENABLE_METHODDEF
Expand All @@ -1441,6 +1479,8 @@ static PyMethodDef GcMethods[] = {
gc_get_referrers__doc__},
{"get_referents", gc_get_referents, METH_VARARGS,
gc_get_referents__doc__},
{"freeze", gc_freeze, METH_NOARGS, gc_freeze__doc__},
{"get_freeze_stats", gc_get_freeze_stats, METH_NOARGS, gc_get_freeze_stats__doc__},
{NULL, NULL} /* Sentinel */
};

Expand Down