Skip to content

Commit c75edab

Browse files
zekun000ambv
authored andcommitted
bpo-31558: Add gc.freeze() (#3705)
Freeze all the objects tracked by gc - move them to a permanent generation and ignore all the future collections. This can be used before a POSIX fork() call to make the gc copy-on-write friendly or to speed up collection.
1 parent bdaeb7d commit c75edab

File tree

5 files changed

+169
-2
lines changed

5 files changed

+169
-2
lines changed

Doc/library/gc.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,33 @@ The :mod:`gc` module provides the following functions:
174174
.. versionadded:: 3.1
175175

176176

177+
.. function:: freeze()
178+
179+
Freeze all the objects tracked by gc - move them to a permanent generation
180+
and ignore all the future collections. This can be used before a POSIX
181+
fork() call to make the gc copy-on-write friendly or to speed up collection.
182+
Also collection before a POSIX fork() call may free pages for future
183+
allocation which can cause copy-on-write too so it's advised to disable gc
184+
in master process and freeze before fork and enable gc in child process.
185+
186+
.. versionadded:: 3.7
187+
188+
189+
.. function:: unfreeze()
190+
191+
Unfreeze the objects in the permanent generation, put them back into the
192+
oldest generation.
193+
194+
.. versionadded:: 3.7
195+
196+
197+
.. function:: get_freeze_count()
198+
199+
Return the number of objects in the permanent generation.
200+
201+
.. versionadded:: 3.7
202+
203+
177204
The following variables are provided for read-only access (you can mutate the
178205
values but should not rebind them):
179206

Include/internal/mem.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ struct _gc_runtime_state {
167167
/* linked lists of container objects */
168168
struct gc_generation generations[NUM_GENERATIONS];
169169
PyGC_Head *generation0;
170+
/* a permanent generation which won't be collected */
171+
struct gc_generation permanent_generation;
170172
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
171173
/* true if we are currently running the collector */
172174
int collecting;

Lib/test/test_gc.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,12 @@ def test_get_stats(self):
734734
self.assertEqual(new[1]["collections"], old[1]["collections"])
735735
self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)
736736

737+
def test_freeze(self):
738+
gc.freeze()
739+
self.assertGreater(gc.get_freeze_count(), 0)
740+
gc.unfreeze()
741+
self.assertEqual(gc.get_freeze_count(), 0)
742+
737743

738744
class GCCallbackTests(unittest.TestCase):
739745
def setUp(self):

Modules/clinic/gcmodule.c.h

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,74 @@ PyDoc_STRVAR(gc_is_tracked__doc__,
255255

256256
#define GC_IS_TRACKED_METHODDEF \
257257
{"is_tracked", (PyCFunction)gc_is_tracked, METH_O, gc_is_tracked__doc__},
258-
/*[clinic end generated code: output=5a58583f00ab018e input=a9049054013a1b77]*/
258+
259+
PyDoc_STRVAR(gc_freeze__doc__,
260+
"freeze($module, /)\n"
261+
"--\n"
262+
"\n"
263+
"Freeze all current tracked objects and ignore them for future collections.\n"
264+
"\n"
265+
"This can be used before a POSIX fork() call to make the gc copy-on-write friendly.\n"
266+
"Note: collection before a POSIX fork() call may free pages for future allocation\n"
267+
"which can cause copy-on-write.");
268+
269+
#define GC_FREEZE_METHODDEF \
270+
{"freeze", (PyCFunction)gc_freeze, METH_NOARGS, gc_freeze__doc__},
271+
272+
static PyObject *
273+
gc_freeze_impl(PyObject *module);
274+
275+
static PyObject *
276+
gc_freeze(PyObject *module, PyObject *Py_UNUSED(ignored))
277+
{
278+
return gc_freeze_impl(module);
279+
}
280+
281+
PyDoc_STRVAR(gc_unfreeze__doc__,
282+
"unfreeze($module, /)\n"
283+
"--\n"
284+
"\n"
285+
"Unfreeze all objects in the permanent generation.\n"
286+
"\n"
287+
"Put all objects in the permanent generation back into oldest generation.");
288+
289+
#define GC_UNFREEZE_METHODDEF \
290+
{"unfreeze", (PyCFunction)gc_unfreeze, METH_NOARGS, gc_unfreeze__doc__},
291+
292+
static PyObject *
293+
gc_unfreeze_impl(PyObject *module);
294+
295+
static PyObject *
296+
gc_unfreeze(PyObject *module, PyObject *Py_UNUSED(ignored))
297+
{
298+
return gc_unfreeze_impl(module);
299+
}
300+
301+
PyDoc_STRVAR(gc_get_freeze_count__doc__,
302+
"get_freeze_count($module, /)\n"
303+
"--\n"
304+
"\n"
305+
"Return the number of objects in the permanent generation.");
306+
307+
#define GC_GET_FREEZE_COUNT_METHODDEF \
308+
{"get_freeze_count", (PyCFunction)gc_get_freeze_count, METH_NOARGS, gc_get_freeze_count__doc__},
309+
310+
static int
311+
gc_get_freeze_count_impl(PyObject *module);
312+
313+
static PyObject *
314+
gc_get_freeze_count(PyObject *module, PyObject *Py_UNUSED(ignored))
315+
{
316+
PyObject *return_value = NULL;
317+
int _return_value;
318+
319+
_return_value = gc_get_freeze_count_impl(module);
320+
if ((_return_value == -1) && PyErr_Occurred()) {
321+
goto exit;
322+
}
323+
return_value = PyLong_FromLong((long)_return_value);
324+
325+
exit:
326+
return return_value;
327+
}
328+
/*[clinic end generated code: output=4f41ec4588154f2b input=a9049054013a1b77]*/

Modules/gcmodule.c

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ _PyGC_Initialize(struct _gc_runtime_state *state)
7171
state->generations[i] = generations[i];
7272
};
7373
state->generation0 = GEN_HEAD(0);
74+
struct gc_generation permanent_generation = {
75+
{{&state->permanent_generation.head, &state->permanent_generation.head, 0}}, 0, 0
76+
};
77+
state->permanent_generation = permanent_generation;
7478
}
7579

7680
/*--------------------------------------------------------------------------
@@ -813,6 +817,8 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
813817
for (i = 0; i < NUM_GENERATIONS; i++)
814818
PySys_FormatStderr(" %zd",
815819
gc_list_size(GEN_HEAD(i)));
820+
PySys_WriteStderr("\ngc: objects in permanent generation: %zd",
821+
gc_list_size(&_PyRuntime.gc.permanent_generation.head));
816822
t1 = _PyTime_GetMonotonicClock();
817823

818824
PySys_WriteStderr("\n");
@@ -1405,6 +1411,56 @@ gc_is_tracked(PyObject *module, PyObject *obj)
14051411
return result;
14061412
}
14071413

1414+
/*[clinic input]
1415+
gc.freeze
1416+
1417+
Freeze all current tracked objects and ignore them for future collections.
1418+
1419+
This can be used before a POSIX fork() call to make the gc copy-on-write friendly.
1420+
Note: collection before a POSIX fork() call may free pages for future allocation
1421+
which can cause copy-on-write.
1422+
[clinic start generated code]*/
1423+
1424+
static PyObject *
1425+
gc_freeze_impl(PyObject *module)
1426+
/*[clinic end generated code: output=502159d9cdc4c139 input=b602b16ac5febbe5]*/
1427+
{
1428+
for (int i = 0; i < NUM_GENERATIONS; ++i) {
1429+
gc_list_merge(GEN_HEAD(i), &_PyRuntime.gc.permanent_generation.head);
1430+
_PyRuntime.gc.generations[i].count = 0;
1431+
}
1432+
Py_RETURN_NONE;
1433+
}
1434+
1435+
/*[clinic input]
1436+
gc.unfreeze
1437+
1438+
Unfreeze all objects in the permanent generation.
1439+
1440+
Put all objects in the permanent generation back into oldest generation.
1441+
[clinic start generated code]*/
1442+
1443+
static PyObject *
1444+
gc_unfreeze_impl(PyObject *module)
1445+
/*[clinic end generated code: output=1c15f2043b25e169 input=2dd52b170f4cef6c]*/
1446+
{
1447+
gc_list_merge(&_PyRuntime.gc.permanent_generation.head, GEN_HEAD(NUM_GENERATIONS-1));
1448+
Py_RETURN_NONE;
1449+
}
1450+
1451+
/*[clinic input]
1452+
gc.get_freeze_count -> int
1453+
1454+
Return the number of objects in the permanent generation.
1455+
[clinic start generated code]*/
1456+
1457+
static int
1458+
gc_get_freeze_count_impl(PyObject *module)
1459+
/*[clinic end generated code: output=e4e2ebcc77e5cbf3 input=4b759db880a3c6e4]*/
1460+
{
1461+
return gc_list_size(&_PyRuntime.gc.permanent_generation.head);
1462+
}
1463+
14081464

14091465
PyDoc_STRVAR(gc__doc__,
14101466
"This module provides access to the garbage collector for reference cycles.\n"
@@ -1422,7 +1478,10 @@ PyDoc_STRVAR(gc__doc__,
14221478
"get_objects() -- Return a list of all objects tracked by the collector.\n"
14231479
"is_tracked() -- Returns true if a given object is tracked.\n"
14241480
"get_referrers() -- Return the list of objects that refer to an object.\n"
1425-
"get_referents() -- Return the list of objects that an object refers to.\n");
1481+
"get_referents() -- Return the list of objects that an object refers to.\n"
1482+
"freeze() -- Freeze all tracked objects and ignore them for future collections.\n"
1483+
"unfreeze() -- Unfreeze all objects in the permanent generation.\n"
1484+
"get_freeze_count() -- Return the number of objects in the permanent generation.\n");
14261485

14271486
static PyMethodDef GcMethods[] = {
14281487
GC_ENABLE_METHODDEF
@@ -1441,6 +1500,9 @@ static PyMethodDef GcMethods[] = {
14411500
gc_get_referrers__doc__},
14421501
{"get_referents", gc_get_referents, METH_VARARGS,
14431502
gc_get_referents__doc__},
1503+
GC_FREEZE_METHODDEF
1504+
GC_UNFREEZE_METHODDEF
1505+
GC_GET_FREEZE_COUNT_METHODDEF
14441506
{NULL, NULL} /* Sentinel */
14451507
};
14461508

0 commit comments

Comments
 (0)