mirror of
https://github.com/python/cpython.git
synced 2025-09-23 08:53:45 +00:00
bpo-36710: Add runtime parameter in gcmodule.c (GH-12958)
Add 'state' or 'runtime' parameter to functions in gcmodule.c to avoid to rely directly on the global variable _PyRuntime.
This commit is contained in:
parent
10c8e6af91
commit
9db0324712
3 changed files with 166 additions and 142 deletions
|
@ -83,7 +83,7 @@ extern void _PyGILState_Init(
|
||||||
PyThreadState *tstate);
|
PyThreadState *tstate);
|
||||||
extern void _PyGILState_Fini(_PyRuntimeState *runtime);
|
extern void _PyGILState_Fini(_PyRuntimeState *runtime);
|
||||||
|
|
||||||
PyAPI_FUNC(void) _PyGC_DumpShutdownStats(void);
|
PyAPI_FUNC(void) _PyGC_DumpShutdownStats(_PyRuntimeState *runtime);
|
||||||
|
|
||||||
PyAPI_FUNC(_PyInitError) _Py_PreInitializeFromCoreConfig(
|
PyAPI_FUNC(_PyInitError) _Py_PreInitializeFromCoreConfig(
|
||||||
const _PyCoreConfig *coreconfig);
|
const _PyCoreConfig *coreconfig);
|
||||||
|
|
|
@ -123,14 +123,14 @@ static PyObject *gc_str = NULL;
|
||||||
DEBUG_UNCOLLECTABLE | \
|
DEBUG_UNCOLLECTABLE | \
|
||||||
DEBUG_SAVEALL
|
DEBUG_SAVEALL
|
||||||
|
|
||||||
#define GEN_HEAD(n) (&_PyRuntime.gc.generations[n].head)
|
#define GEN_HEAD(state, n) (&(state)->generations[n].head)
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyGC_Initialize(struct _gc_runtime_state *state)
|
_PyGC_Initialize(struct _gc_runtime_state *state)
|
||||||
{
|
{
|
||||||
state->enabled = 1; /* automatic collection enabled? */
|
state->enabled = 1; /* automatic collection enabled? */
|
||||||
|
|
||||||
#define _GEN_HEAD(n) (&state->generations[n].head)
|
#define _GEN_HEAD(n) GEN_HEAD(state, n)
|
||||||
struct gc_generation generations[NUM_GENERATIONS] = {
|
struct gc_generation generations[NUM_GENERATIONS] = {
|
||||||
/* PyGC_Head, threshold, count */
|
/* PyGC_Head, threshold, count */
|
||||||
{{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0},
|
{{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0},
|
||||||
|
@ -140,7 +140,7 @@ _PyGC_Initialize(struct _gc_runtime_state *state)
|
||||||
for (int i = 0; i < NUM_GENERATIONS; i++) {
|
for (int i = 0; i < NUM_GENERATIONS; i++) {
|
||||||
state->generations[i] = generations[i];
|
state->generations[i] = generations[i];
|
||||||
};
|
};
|
||||||
state->generation0 = GEN_HEAD(0);
|
state->generation0 = GEN_HEAD(state, 0);
|
||||||
struct gc_generation permanent_generation = {
|
struct gc_generation permanent_generation = {
|
||||||
{(uintptr_t)&state->permanent_generation.head,
|
{(uintptr_t)&state->permanent_generation.head,
|
||||||
(uintptr_t)&state->permanent_generation.head}, 0, 0
|
(uintptr_t)&state->permanent_generation.head}, 0, 0
|
||||||
|
@ -808,21 +808,22 @@ debug_cycle(const char *msg, PyObject *op)
|
||||||
* merged into the old list regardless.
|
* merged into the old list regardless.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
handle_legacy_finalizers(PyGC_Head *finalizers, PyGC_Head *old)
|
handle_legacy_finalizers(struct _gc_runtime_state *state,
|
||||||
|
PyGC_Head *finalizers, PyGC_Head *old)
|
||||||
{
|
{
|
||||||
PyGC_Head *gc = GC_NEXT(finalizers);
|
|
||||||
|
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
if (_PyRuntime.gc.garbage == NULL) {
|
|
||||||
_PyRuntime.gc.garbage = PyList_New(0);
|
PyGC_Head *gc = GC_NEXT(finalizers);
|
||||||
if (_PyRuntime.gc.garbage == NULL)
|
if (state->garbage == NULL) {
|
||||||
|
state->garbage = PyList_New(0);
|
||||||
|
if (state->garbage == NULL)
|
||||||
Py_FatalError("gc couldn't create gc.garbage list");
|
Py_FatalError("gc couldn't create gc.garbage list");
|
||||||
}
|
}
|
||||||
for (; gc != finalizers; gc = GC_NEXT(gc)) {
|
for (; gc != finalizers; gc = GC_NEXT(gc)) {
|
||||||
PyObject *op = FROM_GC(gc);
|
PyObject *op = FROM_GC(gc);
|
||||||
|
|
||||||
if ((_PyRuntime.gc.debug & DEBUG_SAVEALL) || has_legacy_finalizer(op)) {
|
if ((state->debug & DEBUG_SAVEALL) || has_legacy_finalizer(op)) {
|
||||||
if (PyList_Append(_PyRuntime.gc.garbage, op) < 0) {
|
if (PyList_Append(state->garbage, op) < 0) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -904,11 +905,11 @@ check_garbage(PyGC_Head *collectable)
|
||||||
* objects may be freed. It is possible I screwed something up here.
|
* objects may be freed. It is possible I screwed something up here.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
|
delete_garbage(struct _gc_runtime_state *state,
|
||||||
|
PyGC_Head *collectable, PyGC_Head *old)
|
||||||
{
|
{
|
||||||
inquiry clear;
|
|
||||||
|
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
|
|
||||||
while (!gc_list_is_empty(collectable)) {
|
while (!gc_list_is_empty(collectable)) {
|
||||||
PyGC_Head *gc = GC_NEXT(collectable);
|
PyGC_Head *gc = GC_NEXT(collectable);
|
||||||
PyObject *op = FROM_GC(gc);
|
PyObject *op = FROM_GC(gc);
|
||||||
|
@ -916,13 +917,14 @@ delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
|
||||||
_PyObject_ASSERT_WITH_MSG(op, Py_REFCNT(op) > 0,
|
_PyObject_ASSERT_WITH_MSG(op, Py_REFCNT(op) > 0,
|
||||||
"refcount is too small");
|
"refcount is too small");
|
||||||
|
|
||||||
if (_PyRuntime.gc.debug & DEBUG_SAVEALL) {
|
if (state->debug & DEBUG_SAVEALL) {
|
||||||
assert(_PyRuntime.gc.garbage != NULL);
|
assert(state->garbage != NULL);
|
||||||
if (PyList_Append(_PyRuntime.gc.garbage, op) < 0) {
|
if (PyList_Append(state->garbage, op) < 0) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
inquiry clear;
|
||||||
if ((clear = Py_TYPE(op)->tp_clear) != NULL) {
|
if ((clear = Py_TYPE(op)->tp_clear) != NULL) {
|
||||||
Py_INCREF(op);
|
Py_INCREF(op);
|
||||||
(void) clear(op);
|
(void) clear(op);
|
||||||
|
@ -965,8 +967,8 @@ clear_freelists(void)
|
||||||
/* This is the main function. Read this to understand how the
|
/* This is the main function. Read this to understand how the
|
||||||
* collection process works. */
|
* collection process works. */
|
||||||
static Py_ssize_t
|
static Py_ssize_t
|
||||||
collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
collect(struct _gc_runtime_state *state, int generation,
|
||||||
int nofail)
|
Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
Py_ssize_t m = 0; /* # objects collected */
|
Py_ssize_t m = 0; /* # objects collected */
|
||||||
|
@ -978,17 +980,15 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
PyGC_Head *gc;
|
PyGC_Head *gc;
|
||||||
_PyTime_t t1 = 0; /* initialize to prevent a compiler warning */
|
_PyTime_t t1 = 0; /* initialize to prevent a compiler warning */
|
||||||
|
|
||||||
struct gc_generation_stats *stats = &_PyRuntime.gc.generation_stats[generation];
|
if (state->debug & DEBUG_STATS) {
|
||||||
|
|
||||||
if (_PyRuntime.gc.debug & DEBUG_STATS) {
|
|
||||||
PySys_WriteStderr("gc: collecting generation %d...\n",
|
PySys_WriteStderr("gc: collecting generation %d...\n",
|
||||||
generation);
|
generation);
|
||||||
PySys_WriteStderr("gc: objects in each generation:");
|
PySys_WriteStderr("gc: objects in each generation:");
|
||||||
for (i = 0; i < NUM_GENERATIONS; i++)
|
for (i = 0; i < NUM_GENERATIONS; i++)
|
||||||
PySys_FormatStderr(" %zd",
|
PySys_FormatStderr(" %zd",
|
||||||
gc_list_size(GEN_HEAD(i)));
|
gc_list_size(GEN_HEAD(state, i)));
|
||||||
PySys_WriteStderr("\ngc: objects in permanent generation: %zd",
|
PySys_WriteStderr("\ngc: objects in permanent generation: %zd",
|
||||||
gc_list_size(&_PyRuntime.gc.permanent_generation.head));
|
gc_list_size(&state->permanent_generation.head));
|
||||||
t1 = _PyTime_GetMonotonicClock();
|
t1 = _PyTime_GetMonotonicClock();
|
||||||
|
|
||||||
PySys_WriteStderr("\n");
|
PySys_WriteStderr("\n");
|
||||||
|
@ -999,19 +999,19 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
|
|
||||||
/* update collection and allocation counters */
|
/* update collection and allocation counters */
|
||||||
if (generation+1 < NUM_GENERATIONS)
|
if (generation+1 < NUM_GENERATIONS)
|
||||||
_PyRuntime.gc.generations[generation+1].count += 1;
|
state->generations[generation+1].count += 1;
|
||||||
for (i = 0; i <= generation; i++)
|
for (i = 0; i <= generation; i++)
|
||||||
_PyRuntime.gc.generations[i].count = 0;
|
state->generations[i].count = 0;
|
||||||
|
|
||||||
/* merge younger generations with one we are currently collecting */
|
/* merge younger generations with one we are currently collecting */
|
||||||
for (i = 0; i < generation; i++) {
|
for (i = 0; i < generation; i++) {
|
||||||
gc_list_merge(GEN_HEAD(i), GEN_HEAD(generation));
|
gc_list_merge(GEN_HEAD(state, i), GEN_HEAD(state, generation));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handy references */
|
/* handy references */
|
||||||
young = GEN_HEAD(generation);
|
young = GEN_HEAD(state, generation);
|
||||||
if (generation < NUM_GENERATIONS-1)
|
if (generation < NUM_GENERATIONS-1)
|
||||||
old = GEN_HEAD(generation+1);
|
old = GEN_HEAD(state, generation+1);
|
||||||
else
|
else
|
||||||
old = young;
|
old = young;
|
||||||
|
|
||||||
|
@ -1039,7 +1039,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
/* Move reachable objects to next generation. */
|
/* Move reachable objects to next generation. */
|
||||||
if (young != old) {
|
if (young != old) {
|
||||||
if (generation == NUM_GENERATIONS - 2) {
|
if (generation == NUM_GENERATIONS - 2) {
|
||||||
_PyRuntime.gc.long_lived_pending += gc_list_size(young);
|
state->long_lived_pending += gc_list_size(young);
|
||||||
}
|
}
|
||||||
gc_list_merge(young, old);
|
gc_list_merge(young, old);
|
||||||
}
|
}
|
||||||
|
@ -1047,8 +1047,8 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
/* We only untrack dicts in full collections, to avoid quadratic
|
/* We only untrack dicts in full collections, to avoid quadratic
|
||||||
dict build-up. See issue #14775. */
|
dict build-up. See issue #14775. */
|
||||||
untrack_dicts(young);
|
untrack_dicts(young);
|
||||||
_PyRuntime.gc.long_lived_pending = 0;
|
state->long_lived_pending = 0;
|
||||||
_PyRuntime.gc.long_lived_total = gc_list_size(young);
|
state->long_lived_total = gc_list_size(young);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All objects in unreachable are trash, but objects reachable from
|
/* All objects in unreachable are trash, but objects reachable from
|
||||||
|
@ -1072,7 +1072,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
*/
|
*/
|
||||||
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
||||||
m++;
|
m++;
|
||||||
if (_PyRuntime.gc.debug & DEBUG_COLLECTABLE) {
|
if (state->debug & DEBUG_COLLECTABLE) {
|
||||||
debug_cycle("collectable", FROM_GC(gc));
|
debug_cycle("collectable", FROM_GC(gc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1094,17 +1094,17 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
* the reference cycles to be broken. It may also cause some objects
|
* the reference cycles to be broken. It may also cause some objects
|
||||||
* in finalizers to be freed.
|
* in finalizers to be freed.
|
||||||
*/
|
*/
|
||||||
delete_garbage(&unreachable, old);
|
delete_garbage(state, &unreachable, old);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Collect statistics on uncollectable objects found and print
|
/* Collect statistics on uncollectable objects found and print
|
||||||
* debugging information. */
|
* debugging information. */
|
||||||
for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) {
|
for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) {
|
||||||
n++;
|
n++;
|
||||||
if (_PyRuntime.gc.debug & DEBUG_UNCOLLECTABLE)
|
if (state->debug & DEBUG_UNCOLLECTABLE)
|
||||||
debug_cycle("uncollectable", FROM_GC(gc));
|
debug_cycle("uncollectable", FROM_GC(gc));
|
||||||
}
|
}
|
||||||
if (_PyRuntime.gc.debug & DEBUG_STATS) {
|
if (state->debug & DEBUG_STATS) {
|
||||||
_PyTime_t t2 = _PyTime_GetMonotonicClock();
|
_PyTime_t t2 = _PyTime_GetMonotonicClock();
|
||||||
|
|
||||||
if (m == 0 && n == 0)
|
if (m == 0 && n == 0)
|
||||||
|
@ -1121,7 +1121,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
* reachable list of garbage. The programmer has to deal with
|
* reachable list of garbage. The programmer has to deal with
|
||||||
* this if they insist on creating this type of structure.
|
* this if they insist on creating this type of structure.
|
||||||
*/
|
*/
|
||||||
handle_legacy_finalizers(&finalizers, old);
|
handle_legacy_finalizers(state, &finalizers, old);
|
||||||
validate_list(old, 0);
|
validate_list(old, 0);
|
||||||
|
|
||||||
/* Clear free list only during the collection of the highest
|
/* Clear free list only during the collection of the highest
|
||||||
|
@ -1143,16 +1143,21 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update stats */
|
/* Update stats */
|
||||||
if (n_collected)
|
if (n_collected) {
|
||||||
*n_collected = m;
|
*n_collected = m;
|
||||||
if (n_uncollectable)
|
}
|
||||||
|
if (n_uncollectable) {
|
||||||
*n_uncollectable = n;
|
*n_uncollectable = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct gc_generation_stats *stats = &state->generation_stats[generation];
|
||||||
stats->collections++;
|
stats->collections++;
|
||||||
stats->collected += m;
|
stats->collected += m;
|
||||||
stats->uncollectable += n;
|
stats->uncollectable += n;
|
||||||
|
|
||||||
if (PyDTrace_GC_DONE_ENABLED())
|
if (PyDTrace_GC_DONE_ENABLED()) {
|
||||||
PyDTrace_GC_DONE(n+m);
|
PyDTrace_GC_DONE(n+m);
|
||||||
|
}
|
||||||
|
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
return n+m;
|
return n+m;
|
||||||
|
@ -1162,19 +1167,21 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
* is starting or stopping
|
* is starting or stopping
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
invoke_gc_callback(const char *phase, int generation,
|
invoke_gc_callback(struct _gc_runtime_state *state, const char *phase,
|
||||||
Py_ssize_t collected, Py_ssize_t uncollectable)
|
int generation, Py_ssize_t collected,
|
||||||
|
Py_ssize_t uncollectable)
|
||||||
{
|
{
|
||||||
Py_ssize_t i;
|
|
||||||
PyObject *info = NULL;
|
|
||||||
|
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
|
|
||||||
/* we may get called very early */
|
/* we may get called very early */
|
||||||
if (_PyRuntime.gc.callbacks == NULL)
|
if (state->callbacks == NULL) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* The local variable cannot be rebound, check it for sanity */
|
/* The local variable cannot be rebound, check it for sanity */
|
||||||
assert(PyList_CheckExact(_PyRuntime.gc.callbacks));
|
assert(PyList_CheckExact(state->callbacks));
|
||||||
if (PyList_GET_SIZE(_PyRuntime.gc.callbacks) != 0) {
|
PyObject *info = NULL;
|
||||||
|
if (PyList_GET_SIZE(state->callbacks) != 0) {
|
||||||
info = Py_BuildValue("{sisnsn}",
|
info = Py_BuildValue("{sisnsn}",
|
||||||
"generation", generation,
|
"generation", generation,
|
||||||
"collected", collected,
|
"collected", collected,
|
||||||
|
@ -1184,8 +1191,8 @@ invoke_gc_callback(const char *phase, int generation,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i=0; i<PyList_GET_SIZE(_PyRuntime.gc.callbacks); i++) {
|
for (Py_ssize_t i=0; i<PyList_GET_SIZE(state->callbacks); i++) {
|
||||||
PyObject *r, *cb = PyList_GET_ITEM(_PyRuntime.gc.callbacks, i);
|
PyObject *r, *cb = PyList_GET_ITEM(state->callbacks, i);
|
||||||
Py_INCREF(cb); /* make sure cb doesn't go away */
|
Py_INCREF(cb); /* make sure cb doesn't go away */
|
||||||
r = PyObject_CallFunction(cb, "sO", phase, info);
|
r = PyObject_CallFunction(cb, "sO", phase, info);
|
||||||
if (r == NULL) {
|
if (r == NULL) {
|
||||||
|
@ -1204,36 +1211,34 @@ invoke_gc_callback(const char *phase, int generation,
|
||||||
* progress callbacks.
|
* progress callbacks.
|
||||||
*/
|
*/
|
||||||
static Py_ssize_t
|
static Py_ssize_t
|
||||||
collect_with_callback(int generation)
|
collect_with_callback(struct _gc_runtime_state *state, int generation)
|
||||||
{
|
{
|
||||||
Py_ssize_t result, collected, uncollectable;
|
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
invoke_gc_callback("start", generation, 0, 0);
|
Py_ssize_t result, collected, uncollectable;
|
||||||
result = collect(generation, &collected, &uncollectable, 0);
|
invoke_gc_callback(state, "start", generation, 0, 0);
|
||||||
invoke_gc_callback("stop", generation, collected, uncollectable);
|
result = collect(state, generation, &collected, &uncollectable, 0);
|
||||||
|
invoke_gc_callback(state, "stop", generation, collected, uncollectable);
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Py_ssize_t
|
static Py_ssize_t
|
||||||
collect_generations(void)
|
collect_generations(struct _gc_runtime_state *state)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
Py_ssize_t n = 0;
|
|
||||||
|
|
||||||
/* Find the oldest generation (highest numbered) where the count
|
/* Find the oldest generation (highest numbered) where the count
|
||||||
* exceeds the threshold. Objects in the that generation and
|
* exceeds the threshold. Objects in the that generation and
|
||||||
* generations younger than it will be collected. */
|
* generations younger than it will be collected. */
|
||||||
for (i = NUM_GENERATIONS-1; i >= 0; i--) {
|
Py_ssize_t n = 0;
|
||||||
if (_PyRuntime.gc.generations[i].count > _PyRuntime.gc.generations[i].threshold) {
|
for (int i = NUM_GENERATIONS-1; i >= 0; i--) {
|
||||||
|
if (state->generations[i].count > state->generations[i].threshold) {
|
||||||
/* Avoid quadratic performance degradation in number
|
/* Avoid quadratic performance degradation in number
|
||||||
of tracked objects. See comments at the beginning
|
of tracked objects. See comments at the beginning
|
||||||
of this file, and issue #4074.
|
of this file, and issue #4074.
|
||||||
*/
|
*/
|
||||||
if (i == NUM_GENERATIONS - 1
|
if (i == NUM_GENERATIONS - 1
|
||||||
&& _PyRuntime.gc.long_lived_pending < _PyRuntime.gc.long_lived_total / 4)
|
&& state->long_lived_pending < state->long_lived_total / 4)
|
||||||
continue;
|
continue;
|
||||||
n = collect_with_callback(i);
|
n = collect_with_callback(state, i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1301,21 +1306,23 @@ static Py_ssize_t
|
||||||
gc_collect_impl(PyObject *module, int generation)
|
gc_collect_impl(PyObject *module, int generation)
|
||||||
/*[clinic end generated code: output=b697e633043233c7 input=40720128b682d879]*/
|
/*[clinic end generated code: output=b697e633043233c7 input=40720128b682d879]*/
|
||||||
{
|
{
|
||||||
Py_ssize_t n;
|
|
||||||
|
|
||||||
if (generation < 0 || generation >= NUM_GENERATIONS) {
|
if (generation < 0 || generation >= NUM_GENERATIONS) {
|
||||||
PyErr_SetString(PyExc_ValueError, "invalid generation");
|
PyErr_SetString(PyExc_ValueError, "invalid generation");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_PyRuntime.gc.collecting)
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
n = 0; /* already collecting, don't do anything */
|
Py_ssize_t n;
|
||||||
else {
|
if (state->collecting) {
|
||||||
_PyRuntime.gc.collecting = 1;
|
/* already collecting, don't do anything */
|
||||||
n = collect_with_callback(generation);
|
n = 0;
|
||||||
_PyRuntime.gc.collecting = 0;
|
}
|
||||||
|
else {
|
||||||
|
state->collecting = 1;
|
||||||
|
n = collect_with_callback(state, generation);
|
||||||
|
state->collecting = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1366,19 +1373,18 @@ PyDoc_STRVAR(gc_set_thresh__doc__,
|
||||||
"collection.\n");
|
"collection.\n");
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
gc_set_thresh(PyObject *self, PyObject *args)
|
gc_set_threshold(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
int i;
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
if (!PyArg_ParseTuple(args, "i|ii:set_threshold",
|
if (!PyArg_ParseTuple(args, "i|ii:set_threshold",
|
||||||
&_PyRuntime.gc.generations[0].threshold,
|
&state->generations[0].threshold,
|
||||||
&_PyRuntime.gc.generations[1].threshold,
|
&state->generations[1].threshold,
|
||||||
&_PyRuntime.gc.generations[2].threshold))
|
&state->generations[2].threshold))
|
||||||
return NULL;
|
return NULL;
|
||||||
for (i = 3; i < NUM_GENERATIONS; i++) {
|
for (int i = 3; i < NUM_GENERATIONS; i++) {
|
||||||
/* generations higher than 2 get the same threshold */
|
/* generations higher than 2 get the same threshold */
|
||||||
_PyRuntime.gc.generations[i].threshold = _PyRuntime.gc.generations[2].threshold;
|
state->generations[i].threshold = state->generations[2].threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1392,10 +1398,11 @@ static PyObject *
|
||||||
gc_get_threshold_impl(PyObject *module)
|
gc_get_threshold_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=7902bc9f41ecbbd8 input=286d79918034d6e6]*/
|
/*[clinic end generated code: output=7902bc9f41ecbbd8 input=286d79918034d6e6]*/
|
||||||
{
|
{
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
return Py_BuildValue("(iii)",
|
return Py_BuildValue("(iii)",
|
||||||
_PyRuntime.gc.generations[0].threshold,
|
state->generations[0].threshold,
|
||||||
_PyRuntime.gc.generations[1].threshold,
|
state->generations[1].threshold,
|
||||||
_PyRuntime.gc.generations[2].threshold);
|
state->generations[2].threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
@ -1408,10 +1415,11 @@ static PyObject *
|
||||||
gc_get_count_impl(PyObject *module)
|
gc_get_count_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=354012e67b16398f input=a392794a08251751]*/
|
/*[clinic end generated code: output=354012e67b16398f input=a392794a08251751]*/
|
||||||
{
|
{
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
return Py_BuildValue("(iii)",
|
return Py_BuildValue("(iii)",
|
||||||
_PyRuntime.gc.generations[0].count,
|
state->generations[0].count,
|
||||||
_PyRuntime.gc.generations[1].count,
|
state->generations[1].count,
|
||||||
_PyRuntime.gc.generations[2].count);
|
state->generations[2].count);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -1454,8 +1462,9 @@ gc_get_referrers(PyObject *self, PyObject *args)
|
||||||
PyObject *result = PyList_New(0);
|
PyObject *result = PyList_New(0);
|
||||||
if (!result) return NULL;
|
if (!result) return NULL;
|
||||||
|
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
for (i = 0; i < NUM_GENERATIONS; i++) {
|
for (i = 0; i < NUM_GENERATIONS; i++) {
|
||||||
if (!(gc_referrers_for(args, GEN_HEAD(i), result))) {
|
if (!(gc_referrers_for(args, GEN_HEAD(state, i), result))) {
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -1517,6 +1526,7 @@ gc_get_objects_impl(PyObject *module, Py_ssize_t generation)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
PyObject* result;
|
PyObject* result;
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
|
|
||||||
result = PyList_New(0);
|
result = PyList_New(0);
|
||||||
if (result == NULL) {
|
if (result == NULL) {
|
||||||
|
@ -1539,7 +1549,7 @@ gc_get_objects_impl(PyObject *module, Py_ssize_t generation)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (append_objects(result, GEN_HEAD(generation))) {
|
if (append_objects(result, GEN_HEAD(state, generation))) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1548,7 +1558,7 @@ gc_get_objects_impl(PyObject *module, Py_ssize_t generation)
|
||||||
|
|
||||||
/* If generation is not passed or None, 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++) {
|
for (i = 0; i < NUM_GENERATIONS; i++) {
|
||||||
if (append_objects(result, GEN_HEAD(i))) {
|
if (append_objects(result, GEN_HEAD(state, i))) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1570,16 +1580,16 @@ gc_get_stats_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=a8ab1d8a5d26f3ab input=1ef4ed9d17b1a470]*/
|
/*[clinic end generated code: output=a8ab1d8a5d26f3ab input=1ef4ed9d17b1a470]*/
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
PyObject *result;
|
|
||||||
struct gc_generation_stats stats[NUM_GENERATIONS], *st;
|
struct gc_generation_stats stats[NUM_GENERATIONS], *st;
|
||||||
|
|
||||||
/* To get consistent values despite allocations while constructing
|
/* To get consistent values despite allocations while constructing
|
||||||
the result list, we use a snapshot of the running stats. */
|
the result list, we use a snapshot of the running stats. */
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
for (i = 0; i < NUM_GENERATIONS; i++) {
|
for (i = 0; i < NUM_GENERATIONS; i++) {
|
||||||
stats[i] = _PyRuntime.gc.generation_stats[i];
|
stats[i] = state->generation_stats[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
result = PyList_New(0);
|
PyObject *result = PyList_New(0);
|
||||||
if (result == NULL)
|
if (result == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
@ -1646,9 +1656,10 @@ static PyObject *
|
||||||
gc_freeze_impl(PyObject *module)
|
gc_freeze_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=502159d9cdc4c139 input=b602b16ac5febbe5]*/
|
/*[clinic end generated code: output=502159d9cdc4c139 input=b602b16ac5febbe5]*/
|
||||||
{
|
{
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
for (int i = 0; i < NUM_GENERATIONS; ++i) {
|
for (int i = 0; i < NUM_GENERATIONS; ++i) {
|
||||||
gc_list_merge(GEN_HEAD(i), &_PyRuntime.gc.permanent_generation.head);
|
gc_list_merge(GEN_HEAD(state, i), &state->permanent_generation.head);
|
||||||
_PyRuntime.gc.generations[i].count = 0;
|
state->generations[i].count = 0;
|
||||||
}
|
}
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
@ -1665,7 +1676,8 @@ static PyObject *
|
||||||
gc_unfreeze_impl(PyObject *module)
|
gc_unfreeze_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=1c15f2043b25e169 input=2dd52b170f4cef6c]*/
|
/*[clinic end generated code: output=1c15f2043b25e169 input=2dd52b170f4cef6c]*/
|
||||||
{
|
{
|
||||||
gc_list_merge(&_PyRuntime.gc.permanent_generation.head, GEN_HEAD(NUM_GENERATIONS-1));
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
|
gc_list_merge(&state->permanent_generation.head, GEN_HEAD(state, NUM_GENERATIONS-1));
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1711,7 +1723,7 @@ static PyMethodDef GcMethods[] = {
|
||||||
GC_SET_DEBUG_METHODDEF
|
GC_SET_DEBUG_METHODDEF
|
||||||
GC_GET_DEBUG_METHODDEF
|
GC_GET_DEBUG_METHODDEF
|
||||||
GC_GET_COUNT_METHODDEF
|
GC_GET_COUNT_METHODDEF
|
||||||
{"set_threshold", gc_set_thresh, METH_VARARGS, gc_set_thresh__doc__},
|
{"set_threshold", gc_set_threshold, METH_VARARGS, gc_set_thresh__doc__},
|
||||||
GC_GET_THRESHOLD_METHODDEF
|
GC_GET_THRESHOLD_METHODDEF
|
||||||
GC_COLLECT_METHODDEF
|
GC_COLLECT_METHODDEF
|
||||||
GC_GET_OBJECTS_METHODDEF
|
GC_GET_OBJECTS_METHODDEF
|
||||||
|
@ -1746,25 +1758,27 @@ PyInit_gc(void)
|
||||||
|
|
||||||
m = PyModule_Create(&gcmodule);
|
m = PyModule_Create(&gcmodule);
|
||||||
|
|
||||||
if (m == NULL)
|
if (m == NULL) {
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (_PyRuntime.gc.garbage == NULL) {
|
|
||||||
_PyRuntime.gc.garbage = PyList_New(0);
|
|
||||||
if (_PyRuntime.gc.garbage == NULL)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
Py_INCREF(_PyRuntime.gc.garbage);
|
|
||||||
if (PyModule_AddObject(m, "garbage", _PyRuntime.gc.garbage) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (_PyRuntime.gc.callbacks == NULL) {
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
_PyRuntime.gc.callbacks = PyList_New(0);
|
if (state->garbage == NULL) {
|
||||||
if (_PyRuntime.gc.callbacks == NULL)
|
state->garbage = PyList_New(0);
|
||||||
|
if (state->garbage == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
Py_INCREF(_PyRuntime.gc.callbacks);
|
Py_INCREF(state->garbage);
|
||||||
if (PyModule_AddObject(m, "callbacks", _PyRuntime.gc.callbacks) < 0)
|
if (PyModule_AddObject(m, "garbage", state->garbage) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (state->callbacks == NULL) {
|
||||||
|
state->callbacks = PyList_New(0);
|
||||||
|
if (state->callbacks == NULL)
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_INCREF(state->callbacks);
|
||||||
|
if (PyModule_AddObject(m, "callbacks", state->callbacks) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
#define ADD_INT(NAME) if (PyModule_AddIntConstant(m, #NAME, NAME) < 0) return NULL
|
#define ADD_INT(NAME) if (PyModule_AddIntConstant(m, #NAME, NAME) < 0) return NULL
|
||||||
|
@ -1781,17 +1795,23 @@ PyInit_gc(void)
|
||||||
Py_ssize_t
|
Py_ssize_t
|
||||||
PyGC_Collect(void)
|
PyGC_Collect(void)
|
||||||
{
|
{
|
||||||
Py_ssize_t n;
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
|
if (!state->enabled) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (_PyRuntime.gc.collecting)
|
Py_ssize_t n;
|
||||||
n = 0; /* already collecting, don't do anything */
|
if (state->collecting) {
|
||||||
|
/* already collecting, don't do anything */
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
PyObject *exc, *value, *tb;
|
PyObject *exc, *value, *tb;
|
||||||
_PyRuntime.gc.collecting = 1;
|
state->collecting = 1;
|
||||||
PyErr_Fetch(&exc, &value, &tb);
|
PyErr_Fetch(&exc, &value, &tb);
|
||||||
n = collect_with_callback(NUM_GENERATIONS - 1);
|
n = collect_with_callback(state, NUM_GENERATIONS - 1);
|
||||||
PyErr_Restore(exc, value, tb);
|
PyErr_Restore(exc, value, tb);
|
||||||
_PyRuntime.gc.collecting = 0;
|
state->collecting = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
|
@ -1800,41 +1820,42 @@ PyGC_Collect(void)
|
||||||
Py_ssize_t
|
Py_ssize_t
|
||||||
_PyGC_CollectIfEnabled(void)
|
_PyGC_CollectIfEnabled(void)
|
||||||
{
|
{
|
||||||
if (!_PyRuntime.gc.enabled)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return PyGC_Collect();
|
return PyGC_Collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_ssize_t
|
Py_ssize_t
|
||||||
_PyGC_CollectNoFail(void)
|
_PyGC_CollectNoFail(void)
|
||||||
{
|
{
|
||||||
|
assert(!PyErr_Occurred());
|
||||||
|
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
Py_ssize_t n;
|
Py_ssize_t n;
|
||||||
|
|
||||||
assert(!PyErr_Occurred());
|
|
||||||
/* Ideally, this function is only called on interpreter shutdown,
|
/* Ideally, this function is only called on interpreter shutdown,
|
||||||
and therefore not recursively. Unfortunately, when there are daemon
|
and therefore not recursively. Unfortunately, when there are daemon
|
||||||
threads, a daemon thread can start a cyclic garbage collection
|
threads, a daemon thread can start a cyclic garbage collection
|
||||||
during interpreter shutdown (and then never finish it).
|
during interpreter shutdown (and then never finish it).
|
||||||
See http://bugs.python.org/issue8713#msg195178 for an example.
|
See http://bugs.python.org/issue8713#msg195178 for an example.
|
||||||
*/
|
*/
|
||||||
if (_PyRuntime.gc.collecting)
|
if (state->collecting) {
|
||||||
n = 0;
|
n = 0;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
_PyRuntime.gc.collecting = 1;
|
state->collecting = 1;
|
||||||
n = collect(NUM_GENERATIONS - 1, NULL, NULL, 1);
|
n = collect(state, NUM_GENERATIONS - 1, NULL, NULL, 1);
|
||||||
_PyRuntime.gc.collecting = 0;
|
state->collecting = 0;
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyGC_DumpShutdownStats(void)
|
_PyGC_DumpShutdownStats(_PyRuntimeState *runtime)
|
||||||
{
|
{
|
||||||
if (!(_PyRuntime.gc.debug & DEBUG_SAVEALL)
|
struct _gc_runtime_state *state = &runtime->gc;
|
||||||
&& _PyRuntime.gc.garbage != NULL && PyList_GET_SIZE(_PyRuntime.gc.garbage) > 0) {
|
if (!(state->debug & DEBUG_SAVEALL)
|
||||||
|
&& state->garbage != NULL && PyList_GET_SIZE(state->garbage) > 0) {
|
||||||
const char *message;
|
const char *message;
|
||||||
if (_PyRuntime.gc.debug & DEBUG_UNCOLLECTABLE)
|
if (state->debug & DEBUG_UNCOLLECTABLE)
|
||||||
message = "gc: %zd uncollectable objects at " \
|
message = "gc: %zd uncollectable objects at " \
|
||||||
"shutdown";
|
"shutdown";
|
||||||
else
|
else
|
||||||
|
@ -1845,13 +1866,13 @@ _PyGC_DumpShutdownStats(void)
|
||||||
already. */
|
already. */
|
||||||
if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0,
|
if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0,
|
||||||
"gc", NULL, message,
|
"gc", NULL, message,
|
||||||
PyList_GET_SIZE(_PyRuntime.gc.garbage)))
|
PyList_GET_SIZE(state->garbage)))
|
||||||
PyErr_WriteUnraisable(NULL);
|
PyErr_WriteUnraisable(NULL);
|
||||||
if (_PyRuntime.gc.debug & DEBUG_UNCOLLECTABLE) {
|
if (state->debug & DEBUG_UNCOLLECTABLE) {
|
||||||
PyObject *repr = NULL, *bytes = NULL;
|
PyObject *repr = NULL, *bytes = NULL;
|
||||||
repr = PyObject_Repr(_PyRuntime.gc.garbage);
|
repr = PyObject_Repr(state->garbage);
|
||||||
if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr)))
|
if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr)))
|
||||||
PyErr_WriteUnraisable(_PyRuntime.gc.garbage);
|
PyErr_WriteUnraisable(state->garbage);
|
||||||
else {
|
else {
|
||||||
PySys_WriteStderr(
|
PySys_WriteStderr(
|
||||||
" %s\n",
|
" %s\n",
|
||||||
|
@ -1867,8 +1888,9 @@ _PyGC_DumpShutdownStats(void)
|
||||||
void
|
void
|
||||||
_PyGC_Fini(_PyRuntimeState *runtime)
|
_PyGC_Fini(_PyRuntimeState *runtime)
|
||||||
{
|
{
|
||||||
struct _gc_runtime_state *gc = &runtime->gc;
|
struct _gc_runtime_state *state = &runtime->gc;
|
||||||
Py_CLEAR(gc->callbacks);
|
Py_CLEAR(state->garbage);
|
||||||
|
Py_CLEAR(state->callbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for debugging */
|
/* for debugging */
|
||||||
|
@ -1908,6 +1930,7 @@ PyObject_GC_UnTrack(void *op_raw)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
|
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
|
||||||
{
|
{
|
||||||
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
PyObject *op;
|
PyObject *op;
|
||||||
PyGC_Head *g;
|
PyGC_Head *g;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
@ -1923,15 +1946,15 @@ _PyObject_GC_Alloc(int use_calloc, size_t basicsize)
|
||||||
assert(((uintptr_t)g & 3) == 0); // g must be aligned 4bytes boundary
|
assert(((uintptr_t)g & 3) == 0); // g must be aligned 4bytes boundary
|
||||||
g->_gc_next = 0;
|
g->_gc_next = 0;
|
||||||
g->_gc_prev = 0;
|
g->_gc_prev = 0;
|
||||||
_PyRuntime.gc.generations[0].count++; /* number of allocated GC objects */
|
state->generations[0].count++; /* number of allocated GC objects */
|
||||||
if (_PyRuntime.gc.generations[0].count > _PyRuntime.gc.generations[0].threshold &&
|
if (state->generations[0].count > state->generations[0].threshold &&
|
||||||
_PyRuntime.gc.enabled &&
|
state->enabled &&
|
||||||
_PyRuntime.gc.generations[0].threshold &&
|
state->generations[0].threshold &&
|
||||||
!_PyRuntime.gc.collecting &&
|
!state->collecting &&
|
||||||
!PyErr_Occurred()) {
|
!PyErr_Occurred()) {
|
||||||
_PyRuntime.gc.collecting = 1;
|
state->collecting = 1;
|
||||||
collect_generations();
|
collect_generations(state);
|
||||||
_PyRuntime.gc.collecting = 0;
|
state->collecting = 0;
|
||||||
}
|
}
|
||||||
op = FROM_GC(g);
|
op = FROM_GC(g);
|
||||||
return op;
|
return op;
|
||||||
|
@ -2000,8 +2023,9 @@ PyObject_GC_Del(void *op)
|
||||||
if (_PyObject_GC_IS_TRACKED(op)) {
|
if (_PyObject_GC_IS_TRACKED(op)) {
|
||||||
gc_list_remove(g);
|
gc_list_remove(g);
|
||||||
}
|
}
|
||||||
if (_PyRuntime.gc.generations[0].count > 0) {
|
struct _gc_runtime_state *state = &_PyRuntime.gc;
|
||||||
_PyRuntime.gc.generations[0].count--;
|
if (state->generations[0].count > 0) {
|
||||||
|
state->generations[0].count--;
|
||||||
}
|
}
|
||||||
PyObject_FREE(g);
|
PyObject_FREE(g);
|
||||||
}
|
}
|
||||||
|
|
|
@ -535,7 +535,7 @@ PyImport_Cleanup(void)
|
||||||
_PyGC_CollectNoFail();
|
_PyGC_CollectNoFail();
|
||||||
/* Dump GC stats before it's too late, since it uses the warnings
|
/* Dump GC stats before it's too late, since it uses the warnings
|
||||||
machinery. */
|
machinery. */
|
||||||
_PyGC_DumpShutdownStats();
|
_PyGC_DumpShutdownStats(&_PyRuntime);
|
||||||
|
|
||||||
/* Now, if there are any modules left alive, clear their globals to
|
/* Now, if there are any modules left alive, clear their globals to
|
||||||
minimize potential leaks. All C extension modules actually end
|
minimize potential leaks. All C extension modules actually end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue