diff --git a/Include/objimpl.h b/Include/objimpl.h index 860030f9567..003dfdfa5ec 100644 --- a/Include/objimpl.h +++ b/Include/objimpl.h @@ -203,7 +203,6 @@ extern DL_IMPORT(void) _PyObject_Del Py_PROTO((PyObject *)); ( (type *) PyObject_InitVar( \ (PyVarObject *) PyObject_MALLOC( _PyObject_VAR_SIZE((typeobj),(n)) ),\ (typeobj), (n)) ) -#define PyObject_DEL(op) PyObject_FREE(op) /* This example code implements an object constructor with a custom allocator, where PyObject_New is inlined, and shows the important @@ -234,11 +233,67 @@ extern DL_IMPORT(void) _PyObject_Del Py_PROTO((PyObject *)); the 1st step is performed automatically for you, so in a C++ class constructor you would start directly with PyObject_Init/InitVar. */ +/* + * Garbage Collection Support + * ========================== + */ +/* To make a new object participate in garbage collection use + PyObject_{New, VarNew, Del} to manage the memory. Set the type flag + Py_TPFLAGS_GC and define the type method tp_recurse. You should also + add the method tp_clear if your object is mutable. Include + PyGC_INFO_SIZE in the calculation of tp_basicsize. Call + PyObject_GC_Init after the pointers followed by tp_recurse become + valid (usually just before returning the object from the allocation + method. Call PyObject_GC_Fini before those pointers become invalid + (usually at the top of the deallocation method). */ #ifndef WITH_CYCLE_GC -#define PyGC_INFO_SIZE 0 -#endif + +#define PyGC_HEAD_SIZE 0 +#define PyObject_GC_Init(op) +#define PyObject_GC_Fini(op) +#define PyObject_AS_GC(op) (op) +#define PyObject_FROM_GC(op) (op) +#define PyObject_DEL(op) PyObject_FREE(op) + +#else + +/* Add the object into the container set */ +extern DL_IMPORT(void) _PyGC_Insert Py_PROTO((PyObject *)); + +/* Remove the object from the container set */ +extern DL_IMPORT(void) _PyGC_Remove Py_PROTO((PyObject *)); + +#define PyObject_GC_Init(op) _PyGC_Insert((PyObject *)op) +#define PyObject_GC_Fini(op) _PyGC_Remove((PyObject *)op) + +/* Structure *prefixed* to container objects participating in GC */ +typedef struct _gc_head { + struct _gc_head *gc_next; + struct _gc_head *gc_prev; + int gc_refs; +} PyGC_Head; + +#define PyGC_HEAD_SIZE sizeof(PyGC_Head) + +/* Test if a type has a GC head */ +#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_GC) + +/* Test if an object has a GC head */ +#define PyObject_IS_GC(o) PyType_IS_GC((o)->ob_type) + +/* Get an object's GC head */ +#define PyObject_AS_GC(o) ((PyGC_Head *)(o)-1) + +/* Get the object given the PyGC_Head */ +#define PyObject_FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1)) + +#define PyObject_DEL(op) PyObject_FREE( PyObject_IS_GC(op) ? \ + (ANY *)PyObject_AS_GC(op) : \ + (ANY *)(op) ) + +#endif /* WITH_CYCLE_GC */ #ifdef __cplusplus } diff --git a/Lib/test/output/test_gc b/Lib/test/output/test_gc new file mode 100644 index 00000000000..c3859280a04 --- /dev/null +++ b/Lib/test/output/test_gc @@ -0,0 +1,11 @@ +test_gc +list 0x831a754 +dict 0x831a754 +list 0x831a754 +tuple 0x831a734 +class 0x831a794 + +a +b +dict 0x831a9bc +func 0x831d9e4 diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py new file mode 100644 index 00000000000..72253f45b42 --- /dev/null +++ b/Lib/test/test_gc.py @@ -0,0 +1,100 @@ +import gc + +def test_list(): + l = [] + l.append(l) + print 'list 0x%x' % id(l) + gc.collect() + del l + assert gc.collect() == 1 + +def test_dict(): + d = {} + d[1] = d + print 'dict 0x%x' % id(d) + gc.collect() + del d + assert gc.collect() == 1 + +def test_tuple(): + l = [] + t = (l,) + l.append(t) + print 'list 0x%x' % id(l) + print 'tuple 0x%x' % id(t) + gc.collect() + del t + del l + assert gc.collect() == 2 + +def test_class(): + class A: + pass + A.a = A + print 'class 0x%x' % id(A) + gc.collect() + del A + assert gc.collect() > 0 + +def test_instance(): + class A: + pass + a = A() + a.a = a + print repr(a) + gc.collect() + del a + assert gc.collect() > 0 + +def test_method(): + class A: + def __init__(self): + self.init = self.__init__ + a = A() + gc.collect() + del a + assert gc.collect() > 0 + +def test_finalizer(): + class A: + def __del__(self): pass + class B: + pass + a = A() + a.a = a + id_a = id(a) + b = B() + b.b = b + print 'a', repr(a) + print 'b', repr(b) + gc.collect() + gc.garbage[:] = [] + del a + del b + assert gc.collect() > 0 + assert id(gc.garbage[0]) == id_a + +def test_function(): + d = {} + exec("def f(): pass\n") in d + print 'dict 0x%x' % id(d) + print 'func 0x%x' % id(d['f']) + gc.collect() + del d + assert gc.collect() == 2 + + +def test_all(): + debug = gc.get_debug() + gc.set_debug(gc.DEBUG_LEAK | gc.DEBUG_STATS) + test_list() + test_dict() + test_tuple() + test_class() + test_instance() + test_method() + test_finalizer() + test_function() + gc.set_debug(debug) + +test_all() diff --git a/Modules/Setup.thread.in b/Modules/Setup.thread.in index d532156a5fb..7360144c275 100644 --- a/Modules/Setup.thread.in +++ b/Modules/Setup.thread.in @@ -10,3 +10,6 @@ # support threads. @USE_THREAD_MODULE@thread threadmodule.c + +# Garbage collection enabled with --with-cycle-gc +@USE_GC_MODULE@gc gcmodule.c diff --git a/Modules/cPickle.c b/Modules/cPickle.c index 0d919368762..f9ef78f980f 100644 --- a/Modules/cPickle.c +++ b/Modules/cPickle.c @@ -2892,7 +2892,7 @@ Instance_New(PyObject *cls, PyObject *args) { Py_DECREF(inst); goto err; } - + PyObject_GC_Init(inst); return (PyObject *)inst; } Py_DECREF(__getinitargs__); diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c new file mode 100644 index 00000000000..8c0dc3d528e --- /dev/null +++ b/Modules/gcmodule.c @@ -0,0 +1,679 @@ +/* + + Reference Cycle Garbage Collection + ================================== + + Neil Schemenauer + + Based on a post on the python-dev list. Ideas from Guido van Rossum, + Eric Tiedemann, and various others. + + http://www.enme.calgary.ca/~nascheme/python/gc.html + http://www.python.org/pipermail/python-dev/2000-March/003869.html + http://www.python.org/pipermail/python-dev/2000-March/004010.html + http://www.python.org/pipermail/python-dev/2000-March/004022.html + + For a highlevel view of the collection process, read the collect + function. + + TODO: + use a different interface for set_debug() (keywords)? + tune parameters + +*/ + + +#include "Python.h" + +#ifdef WITH_CYCLE_GC + +/* magic gc_refs value */ +#define GC_MOVED -1 + +/*** Global GC state ***/ + +/* linked lists of container objects */ +static PyGC_Head generation0 = {&generation0, &generation0, 0}; +static PyGC_Head generation1 = {&generation1, &generation1, 0}; +static PyGC_Head generation2 = {&generation2, &generation2, 0}; +static int generation = 0; /* current generation being collected */ + +/* collection frequencies, XXX tune these */ +static int threshold0 = 100; /* net new containers before collection */ +static int threshold1 = 10; /* generation0 collections before collecting 1 */ +static int threshold2 = 10; /* generation1 collections before collecting 2 */ + +/* net new objects allocated since last collection */ +static int allocated; + +/* set for debugging information */ +#define DEBUG_STATS (1<<0) /* print collection statistics */ +#define DEBUG_COLLECTABLE (1<<1) /* print collectable objects */ +#define DEBUG_UNCOLLECTABLE (1<<2) /* print uncollectable objects */ +#define DEBUG_INSTANCES (1<<3) /* print instances */ +#define DEBUG_OBJECTS (1<<4) /* print other objects */ +#define DEBUG_LEAK DEBUG_COLLECTABLE | \ + DEBUG_UNCOLLECTABLE | \ + DEBUG_INSTANCES | \ + DEBUG_OBJECTS +static int debug; + +/* list of uncollectable objects */ +static PyObject *garbage; + + +/*** list functions ***/ + +static void +gc_list_init(PyGC_Head *list) +{ + list->gc_prev = list; + list->gc_next = list; +} + +static void +gc_list_append(PyGC_Head *node, PyGC_Head *list) +{ + node->gc_next = list; + node->gc_prev = list->gc_prev; + node->gc_prev->gc_next = node; + list->gc_prev = node; +} + +static void +gc_list_remove(PyGC_Head *node) +{ + node->gc_prev->gc_next = node->gc_next; + node->gc_next->gc_prev = node->gc_prev; +#ifdef Py_DEBUG + node->gc_prev = NULL; + node->gc_next = NULL; +#endif +} + +static void +gc_list_move(PyGC_Head *from, PyGC_Head *to) +{ + if (from->gc_next == from) { + /* empty from list */ + gc_list_init(to); + } else { + to->gc_next = from->gc_next; + to->gc_next->gc_prev = to; + to->gc_prev = from->gc_prev; + to->gc_prev->gc_next = to; + } + gc_list_init(from); +} + +/* append a list onto another list, from becomes an empty list */ +static void +gc_list_merge(PyGC_Head *from, PyGC_Head *to) +{ + PyGC_Head *tail; + if (from->gc_next != from) { + tail = to->gc_prev; + tail->gc_next = from->gc_next; + tail->gc_next->gc_prev = tail; + to->gc_prev = from->gc_prev; + to->gc_prev->gc_next = to; + } + gc_list_init(from); +} + +static long +gc_list_size(PyGC_Head *list) +{ + PyGC_Head *gc; + long n = 0; + for (gc = list->gc_next; gc != list; gc = gc->gc_next) { + n++; + } + return n; +} + +/*** end of list stuff ***/ + + +/* Set all gc_refs = ob_refcnt */ +static void +update_refs(PyGC_Head *containers) +{ + PyGC_Head *gc = containers->gc_next; + for (; gc != containers; gc=gc->gc_next) { + gc->gc_refs = PyObject_FROM_GC(gc)->ob_refcnt; + } +} + +static int +visit_decref(PyObject *op, void *data) +{ + if (op && PyObject_IS_GC(op)) { + PyObject_AS_GC(op)->gc_refs--; + } + return 0; +} + +/* Subtract internal references from gc_refs */ +static void +subtract_refs(PyGC_Head *containers) +{ + traverseproc traverse; + PyGC_Head *gc = containers->gc_next; + for (; gc != containers; gc=gc->gc_next) { + traverse = PyObject_FROM_GC(gc)->ob_type->tp_traverse; + (void) traverse(PyObject_FROM_GC(gc), + (visitproc)visit_decref, + NULL); + } +} + +/* Append objects with gc_refs > 0 to roots list */ +static void +move_roots(PyGC_Head *containers, PyGC_Head *roots) +{ + PyGC_Head *next; + PyGC_Head *gc = containers->gc_next; + while (gc != containers) { + next = gc->gc_next; + if (gc->gc_refs > 0) { + gc_list_remove(gc); + gc_list_append(gc, roots); + gc->gc_refs = GC_MOVED; + } + gc = next; + } +} + +static int +visit_reachable(PyObject *op, PyGC_Head *roots) +{ + if (PyObject_IS_GC(op)) { + PyGC_Head *gc = PyObject_AS_GC(op); + if (gc && gc->gc_refs != GC_MOVED) { + gc_list_remove(gc); + gc_list_append(gc, roots); + gc->gc_refs = GC_MOVED; + } + } + return 0; +} + +/* Move objects referenced from reachable to reachable set. */ +static void +move_root_reachable(PyGC_Head *reachable) +{ + traverseproc traverse; + PyGC_Head *gc = reachable->gc_next; + for (; gc != reachable; gc=gc->gc_next) { + /* careful, reachable list is growing here */ + PyObject *op = PyObject_FROM_GC(gc); + traverse = op->ob_type->tp_traverse; + (void) traverse(op, + (visitproc)visit_reachable, + (void *)reachable); + } +} + +/* move all objects with finalizers (instances with __del__) */ +static void +move_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) +{ + PyGC_Head *next; + PyGC_Head *gc = unreachable->gc_next; + static PyObject *delstr; + if (delstr == NULL) { + delstr = PyString_InternFromString("__del__"); + } + for (; gc != unreachable; gc=next) { + PyObject *op = PyObject_FROM_GC(gc); + next = gc->gc_next; + if (PyInstance_Check(op) && PyObject_HasAttr(op, delstr)) { + gc_list_remove(gc); + gc_list_append(gc, finalizers); + } + } +} + + +/* called by tp_traverse */ +static int +visit_finalizer_reachable(PyObject *op, PyGC_Head *finalizers) +{ + if (PyObject_IS_GC(op)) { + PyGC_Head *gc = PyObject_AS_GC(op); + if (gc && gc->gc_refs != GC_MOVED) { + gc_list_remove(gc); + gc_list_append(gc, finalizers); + gc->gc_refs = GC_MOVED; + } + } + return 0; +} + +/* Move objects referenced from roots to roots */ +static void +move_finalizer_reachable(PyGC_Head *finalizers) +{ + traverseproc traverse; + PyGC_Head *gc = finalizers->gc_next; + for (; gc != finalizers; gc=gc->gc_next) { + /* careful, finalizers list is growing here */ + traverse = PyObject_FROM_GC(gc)->ob_type->tp_traverse; + (void) traverse(PyObject_FROM_GC(gc), + (visitproc)visit_finalizer_reachable, + (void *)finalizers); + } +} + +static void +debug_instance(PyObject *output, char *msg, PyInstanceObject *inst) +{ + char buf[200]; + char *cname; + /* be careful not to create new dictionaries */ + PyObject *classname = inst->in_class->cl_name; + if (classname != NULL && PyString_Check(classname)) + cname = PyString_AsString(classname); + else + cname = "?"; + sprintf(buf, "gc: %s<%.100s instance at %lx>\n", + msg, cname, (long)inst); + PyFile_WriteString(buf, output); +} + +static void +debug_cycle(PyObject *output, char *msg, PyObject *op) +{ + if ((debug & DEBUG_INSTANCES) && PyInstance_Check(op)) { + debug_instance(output, msg, (PyInstanceObject *)op); + } else if (debug & DEBUG_OBJECTS) { + char buf[200]; + sprintf(buf, "gc: %s<%s 0x%x>\n", + msg, + op->ob_type->tp_name, + (long)op); + PyFile_WriteString(buf, output); + } +} + +/* Handle uncollectable garbage (cycles with finalizers). */ +static void +handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old) +{ + PyGC_Head *gc; + if (garbage == NULL) { + garbage = PyList_New(0); + } + for (gc = finalizers->gc_next; gc != finalizers; + gc = finalizers->gc_next) { + PyObject *op = PyObject_FROM_GC(gc); + /* Add all instances to a Python accessible list of garbage */ + if (PyInstance_Check(op)) { + PyList_Append(garbage, op); + } + /* We assume that all objects in finalizers are reachable from + * instances. Once we add the instances to the garbage list + * everything is reachable from Python again. */ + gc_list_remove(gc); + gc_list_append(gc, old); + } +} + +/* Break reference cycles by clearing the containers involved. This is + * tricky business as the lists can be changing and we don't know which + * objects may be freed. It is possible I screwed something up here. */ +static void +delete_garbage(PyGC_Head *unreachable, PyGC_Head *old) +{ + inquiry clear; + + while (unreachable->gc_next != unreachable) { + PyGC_Head *gc = unreachable->gc_next; + PyObject *op = PyObject_FROM_GC(gc); + /* + PyList_Append(garbage, op); + */ + if ((clear = op->ob_type->tp_clear) != NULL) { + Py_INCREF(op); + clear((PyObject *)op); + Py_DECREF(op); + } + /* only try to call tp_clear once for each object */ + if (unreachable->gc_next == gc) { + /* still alive, move it, it may die later */ + gc_list_remove(gc); + gc_list_append(gc, old); + } + } +} + +/* This is the main function. Read this to understand how the + * collection process works. */ +static long +collect(PyGC_Head *young, PyGC_Head *old) +{ + long n = 0; + long m = 0; + PyGC_Head reachable; + PyGC_Head unreachable; + PyGC_Head finalizers; + PyGC_Head *gc; + PyObject *output = NULL; + + if (debug) { + output = PySys_GetObject("stderr"); + } + if (debug & DEBUG_STATS) { + char buf[100]; + sprintf(buf, "gc: collecting generation %d...\n", generation); + PyFile_WriteString(buf,output); + sprintf(buf, "gc: objects in each generation: %d %d %d\n", + gc_list_size(&generation0), + gc_list_size(&generation1), + gc_list_size(&generation2)); + PyFile_WriteString(buf,output); + } + + /* Using ob_refcnt and gc_refs, calculate which objects in the + * container set are reachable from outside the set (ie. have a + * refcount greater than 0 when all the references within the + * set are taken into account */ + update_refs(young); + subtract_refs(young); + + /* Move everything reachable from outside the set into the + * reachable set (ie. gc_refs > 0). Next, move everything + * reachable from objects in the reachable set. */ + gc_list_init(&reachable); + move_roots(young, &reachable); + move_root_reachable(&reachable); + + /* move unreachable objects to a temporary list, new objects can be + * allocated after this point */ + gc_list_init(&unreachable); + gc_list_move(young, &unreachable); + + /* move reachable objects to next generation */ + gc_list_merge(&reachable, old); + + /* Move objects reachable from finalizers, we can't safely delete + * them. Python programmers should take care not to create such + * things. For Python finalizers means instance objects with + * __del__ methods. */ + gc_list_init(&finalizers); + move_finalizers(&unreachable, &finalizers); + move_finalizer_reachable(&finalizers); + + /* Collect statistics on collectable objects found and print + * debugging information. */ + for (gc = unreachable.gc_next; gc != &unreachable; + gc = gc->gc_next) { + m++; + if (output != NULL && (debug & DEBUG_COLLECTABLE)) { + debug_cycle(output, "collectable ", PyObject_FROM_GC(gc)); + } + } + /* call tp_clear on objects in the collectable set. This will cause + * the reference cycles to be broken. It may also cause some objects in + * finalizers to be freed */ + delete_garbage(&unreachable, old); + + /* Collect statistics on uncollectable objects found and print + * debugging information. */ + for (gc = finalizers.gc_next; gc != &finalizers; + gc = gc->gc_next) { + n++; + if (output != NULL && (debug & DEBUG_UNCOLLECTABLE)) { + debug_cycle(output, "uncollectable ", PyObject_FROM_GC(gc)); + } + } + if (output != NULL && (debug & DEBUG_STATS)) { + if (m == 0 && n == 0) { + PyFile_WriteString("gc: done.\n", output); + } else { + char buf[200]; + sprintf(buf, + "gc: done, %d unreachable, %d uncollectable.\n", + n+m, n); + PyFile_WriteString(buf, output); + } + } + + /* Append instances in the uncollectable set to a Python + * reachable list of garbage. The programmer has to deal with + * this if they insist on creating this type of structure. */ + handle_finalizers(&finalizers, old); + + allocated = 0; + PyErr_Clear(); /* in case writing to sys.stderr failed */ + return n+m; +} + +static long +collect_generations(void) +{ + static long collections0 = 0; + static long collections1 = 0; + long n; + + + if (collections1 > threshold2) { + generation = 2; + gc_list_merge(&generation0, &generation2); + gc_list_merge(&generation1, &generation2); + if (generation2.gc_next != &generation2) { + n = collect(&generation2, &generation2); + } + collections1 = 0; + } else if (collections0 > threshold1) { + generation = 1; + collections1++; + gc_list_merge(&generation0, &generation1); + if (generation1.gc_next != &generation1) { + n = collect(&generation1, &generation2); + } + collections0 = 0; + } else { + generation = 0; + collections0++; + if (generation0.gc_next != &generation0) { + n = collect(&generation0, &generation1); + } + } + return n; +} + +void +_PyGC_Insert(PyObject *op) +{ + /* collection lock since collecting may cause allocations */ + static int collecting = 0; + +#ifdef Py_DEBUG + if (!PyObject_IS_GC(op)) { + abort(); + } +#endif + if (threshold0 && allocated > threshold0 && !collecting) { + collecting++; + collect_generations(); + collecting--; + } + allocated++; + gc_list_append(PyObject_AS_GC(op), &generation0); +} + +void +_PyGC_Remove(PyObject *op) +{ + PyGC_Head *g = PyObject_AS_GC(op); +#ifdef Py_DEBUG + if (!PyObject_IS_GC(op)) { + abort(); + } +#endif + gc_list_remove(g); + if (allocated > 0) { + allocated--; + } +} + + +static char collect__doc__[] = +"collect() -> n\n" +"\n" +"Run a full collection. The number of unreachable objects is returned.\n" +; + +static PyObject * +Py_collect(self, args) + PyObject *self; + PyObject *args; +{ + long n; + + if(!PyArg_ParseTuple(args, "")) /* check no args */ + return NULL; + + generation = 2; + gc_list_merge(&generation0, &generation2); + gc_list_merge(&generation1, &generation2); + n = collect(&generation2, &generation2); + + return Py_BuildValue("i", n); +} + +static char set_debug__doc__[] = +"set_debug(flags) -> None\n" +"\n" +"Set the garbage collection debugging flags. Debugging information is\n" +"written to sys.stderr.\n" +"\n" +"flags is an integer and can have the following bits turned on:\n" +"\n" +" DEBUG_STATS - Print statistics during collection.\n" +" DEBUG_COLLECTABLE - Print collectable objects found.\n" +" DEBUG_UNCOLLECTABLE - Print unreachable but uncollectable objects found.\n" +" DEBUG_INSTANCES - Print instance objects.\n" +" DEBUG_OBJECTS - Print objects other than instances.\n" +" DEBUG_LEAK - Debug leaking programs (everything but STATS).\n" +; + +static PyObject * +Py_set_debug(self, args) + PyObject *self; + PyObject *args; +{ + if (!PyArg_ParseTuple(args, "l", &debug)) + return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +static char get_debug__doc__[] = +"get_debug() -> flags\n" +"\n" +"Get the garbage collection debugging flags.\n" +; + +static PyObject * +Py_get_debug(self, args) + PyObject *self; + PyObject *args; +{ + if(!PyArg_ParseTuple(args, "")) /* no args */ + return NULL; + + return Py_BuildValue("i", debug); +} + +static char set_thresh__doc__[] = +"set_threshold(threshold0, [threhold1, threshold2]) -> None\n" +"\n" +"Sets the collection thresholds. Setting threshold0 to zero disables\n" +"collection.\n" +; + +static PyObject * +Py_set_thresh(self, args) + PyObject *self; + PyObject *args; +{ + if (!PyArg_ParseTuple(args, "i|ii", &threshold0, + &threshold1, &threshold2)) + return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +static char get_thresh__doc__[] = +"get_threshold() -> (threshold0, threshold1, threshold2)\n" +"\n" +"Return the current collection thresholds\n" +; + +static PyObject * +Py_get_thresh(self, args) + PyObject *self; + PyObject *args; +{ + if(!PyArg_ParseTuple(args, "")) /* no args */ + return NULL; + + return Py_BuildValue("(iii)", threshold0, threshold1, threshold2); +} + + +static char gc__doc__ [] = +"This module provides access to the garbage collector for reference cycles.\n" +"\n" +"collect() -- Do a full collection right now.\n" +"set_debug() -- Set debugging flags.\n" +"get_debug() -- Get debugging flags.\n" +"set_threshold() -- Set the collection thresholds.\n" +"get_threshold() -- Return the current the collection thresholds.\n" +; + +static PyMethodDef GcMethods[] = { + {"set_debug", Py_set_debug, METH_VARARGS, set_debug__doc__}, + {"get_debug", Py_get_debug, METH_VARARGS, get_debug__doc__}, + {"set_threshold", Py_set_thresh, METH_VARARGS, set_thresh__doc__}, + {"get_threshold", Py_get_thresh, METH_VARARGS, get_thresh__doc__}, + {"collect", Py_collect, METH_VARARGS, collect__doc__}, + {NULL, NULL} /* Sentinel */ +}; + +void +initgc(void) +{ + PyObject *m; + PyObject *d; + + m = Py_InitModule4("gc", + GcMethods, + gc__doc__, + NULL, + PYTHON_API_VERSION); + d = PyModule_GetDict(m); + if (garbage == NULL) { + garbage = PyList_New(0); + } + PyDict_SetItemString(d, "garbage", garbage); + PyDict_SetItemString(d, "DEBUG_STATS", + PyInt_FromLong(DEBUG_STATS)); + PyDict_SetItemString(d, "DEBUG_COLLECTABLE", + PyInt_FromLong(DEBUG_COLLECTABLE)); + PyDict_SetItemString(d, "DEBUG_UNCOLLECTABLE", + PyInt_FromLong(DEBUG_UNCOLLECTABLE)); + PyDict_SetItemString(d, "DEBUG_INSTANCES", + PyInt_FromLong(DEBUG_INSTANCES)); + PyDict_SetItemString(d, "DEBUG_OBJECTS", + PyInt_FromLong(DEBUG_OBJECTS)); + PyDict_SetItemString(d, "DEBUG_LEAK", + PyInt_FromLong(DEBUG_LEAK)); +} + +#endif /* WITH_CYCLE_GC */ diff --git a/Modules/newmodule.c b/Modules/newmodule.c index fa0dc8b1d1a..d35896cfd12 100644 --- a/Modules/newmodule.c +++ b/Modules/newmodule.c @@ -56,6 +56,7 @@ new_instance(unused, args) Py_INCREF(dict); inst->in_class = (PyClassObject *)klass; inst->in_dict = dict; + PyObject_GC_Init(inst); return (PyObject *)inst; } diff --git a/Objects/classobject.c b/Objects/classobject.c index f1dabfef5bf..4653d27b604 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -132,6 +132,7 @@ PyClass_New(bases, dict, name) Py_XINCREF(op->cl_getattr); Py_XINCREF(op->cl_setattr); Py_XINCREF(op->cl_delattr); + PyObject_GC_Init(op); return (PyObject *) op; } @@ -141,6 +142,7 @@ static void class_dealloc(op) PyClassObject *op; { + PyObject_GC_Fini(op); Py_DECREF(op->cl_bases); Py_DECREF(op->cl_dict); Py_XDECREF(op->cl_name); @@ -428,7 +430,7 @@ PyTypeObject PyClass_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "class", - sizeof(PyClassObject) + PyGC_INFO_SIZE, + sizeof(PyClassObject) + PyGC_HEAD_SIZE, 0, (destructor)class_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ @@ -490,6 +492,7 @@ PyInstance_New(class, arg, kw) if (inst == NULL) return NULL; inst->in_dict = PyDict_New(); + PyObject_GC_Init(inst); if (inst->in_dict == NULL) { PyObject_DEL(inst); return NULL; @@ -539,11 +542,12 @@ instance_dealloc(inst) PyObject *error_type, *error_value, *error_traceback; PyObject *del; static PyObject *delstr; + extern long _Py_RefTotal; + PyObject_GC_Fini(inst); /* Call the __del__ method if it exists. First temporarily revive the object and save the current exception, if any. */ #ifdef Py_TRACE_REFS /* much too complicated if Py_TRACE_REFS defined */ - extern long _Py_RefTotal; inst->ob_type = &PyInstance_Type; _Py_NewReference((PyObject *)inst); _Py_RefTotal--; /* compensate for increment in NEWREF */ @@ -591,6 +595,7 @@ instance_dealloc(inst) #ifdef COUNT_ALLOCS inst->ob_type->tp_free--; #endif + PyObject_GC_Init((PyObject *)inst); return; /* __del__ added a reference; don't delete now */ } #ifdef Py_TRACE_REFS @@ -598,7 +603,9 @@ instance_dealloc(inst) inst->ob_type->tp_free--; /* compensate for increment in UNREF */ #endif _Py_ForgetReference((PyObject *)inst); +#ifndef WITH_CYCLE_GC inst->ob_type = NULL; +#endif #endif /* Py_TRACE_REFS */ Py_DECREF(inst->in_class); Py_XDECREF(inst->in_dict); @@ -1510,7 +1517,7 @@ PyTypeObject PyInstance_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "instance", - sizeof(PyInstanceObject) + PyGC_INFO_SIZE, + sizeof(PyInstanceObject) + PyGC_HEAD_SIZE, 0, (destructor)instance_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ @@ -1568,6 +1575,7 @@ PyMethod_New(func, self, class) im->im_self = self; Py_INCREF(class); im->im_class = class; + PyObject_GC_Init(im); return (PyObject *)im; } @@ -1643,6 +1651,7 @@ static void instancemethod_dealloc(im) register PyMethodObject *im; { + PyObject_GC_Fini(im); Py_DECREF(im->im_func); Py_XDECREF(im->im_self); Py_DECREF(im->im_class); @@ -1745,7 +1754,7 @@ PyTypeObject PyMethod_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "instance method", - sizeof(PyMethodObject) + PyGC_INFO_SIZE, + sizeof(PyMethodObject) + PyGC_HEAD_SIZE, 0, (destructor)instancemethod_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index ce38f11431e..ea3af486e3b 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -129,6 +129,7 @@ PyDict_New() mp->ma_table = NULL; mp->ma_fill = 0; mp->ma_used = 0; + PyObject_GC_Init(mp); return (PyObject *)mp; } @@ -481,6 +482,7 @@ dict_dealloc(mp) register int i; register dictentry *ep; Py_TRASHCAN_SAFE_BEGIN(mp) + PyObject_GC_Fini(mp); for (i = 0, ep = mp->ma_table; i < mp->ma_size; i++, ep++) { if (ep->me_key != NULL) { Py_DECREF(ep->me_key); @@ -1087,7 +1089,7 @@ PyTypeObject PyDict_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "dictionary", - sizeof(dictobject) + PyGC_INFO_SIZE, + sizeof(dictobject) + PyGC_HEAD_SIZE, 0, (destructor)dict_dealloc, /*tp_dealloc*/ (printfunc)dict_print, /*tp_print*/ diff --git a/Objects/funcobject.c b/Objects/funcobject.c index d9ce0271dc0..2736d6530e6 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -63,6 +63,7 @@ PyFunction_New(code, globals) Py_INCREF(doc); op->func_doc = doc; } + PyObject_GC_Init(op); return (PyObject *)op; } @@ -186,6 +187,7 @@ static void func_dealloc(op) PyFunctionObject *op; { + PyObject_GC_Fini(op); Py_DECREF(op->func_code); Py_DECREF(op->func_globals); Py_DECREF(op->func_name); @@ -277,7 +279,7 @@ PyTypeObject PyFunction_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "function", - sizeof(PyFunctionObject) + PyGC_INFO_SIZE, + sizeof(PyFunctionObject) + PyGC_HEAD_SIZE, 0, (destructor)func_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ diff --git a/Objects/listobject.c b/Objects/listobject.c index e9f12abf29b..a75100e7fbd 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -72,10 +72,11 @@ PyList_New(size) } /* PyObject_NewVar is inlined */ op = (PyListObject *) PyObject_MALLOC(sizeof(PyListObject) - + PyGC_INFO_SIZE); + + PyGC_HEAD_SIZE); if (op == NULL) { return PyErr_NoMemory(); } + op = (PyListObject *) PyObject_FROM_GC(op); if (size <= 0) { op->ob_item = NULL; } @@ -89,6 +90,7 @@ PyList_New(size) PyObject_INIT_VAR(op, &PyList_Type, size); for (i = 0; i < size; i++) op->ob_item[i] = NULL; + PyObject_GC_Init(op); return (PyObject *) op; } @@ -216,6 +218,7 @@ list_dealloc(op) { int i; Py_TRASHCAN_SAFE_BEGIN(op) + PyObject_GC_Fini(op); if (op->ob_item != NULL) { /* Do it backwards, for Christian Tismer. There's a simple test case where somehow this reduces @@ -1498,7 +1501,7 @@ PyTypeObject PyList_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "list", - sizeof(PyListObject) + PyGC_INFO_SIZE, + sizeof(PyListObject) + PyGC_HEAD_SIZE, 0, (destructor)list_dealloc, /*tp_dealloc*/ (printfunc)list_print, /*tp_print*/ @@ -1577,7 +1580,7 @@ static PyTypeObject immutable_list_type = { PyObject_HEAD_INIT(&PyType_Type) 0, "list (immutable, during sort)", - sizeof(PyListObject) + PyGC_INFO_SIZE, + sizeof(PyListObject) + PyGC_HEAD_SIZE, 0, 0, /*tp_dealloc*/ /* Cannot happen */ (printfunc)list_print, /*tp_print*/ diff --git a/Objects/object.c b/Objects/object.c index 9ed03b2fbe9..6eaff67dacd 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -124,6 +124,10 @@ PyObject_Init(op, tp) "NULL object passed to PyObject_Init"); return op; } +#ifdef WITH_CYCLE_GC + if (PyType_IS_GC(tp)) + op = (PyObject *) PyObject_FROM_GC(op); +#endif /* Any changes should be reflected in PyObject_INIT (objimpl.h) */ op->ob_type = tp; _Py_NewReference(op); @@ -141,6 +145,10 @@ PyObject_InitVar(op, tp, size) "NULL object passed to PyObject_InitVar"); return op; } +#ifdef WITH_CYCLE_GC + if (PyType_IS_GC(tp)) + op = (PyVarObject *) PyObject_FROM_GC(op); +#endif /* Any changes should be reflected in PyObject_INIT_VAR */ op->ob_size = size; op->ob_type = tp; @@ -156,6 +164,10 @@ _PyObject_New(tp) op = (PyObject *) PyObject_MALLOC(_PyObject_SIZE(tp)); if (op == NULL) return PyErr_NoMemory(); +#ifdef WITH_CYCLE_GC + if (PyType_IS_GC(tp)) + op = (PyObject *) PyObject_FROM_GC(op); +#endif return PyObject_INIT(op, tp); } @@ -168,6 +180,10 @@ _PyObject_NewVar(tp, size) op = (PyVarObject *) PyObject_MALLOC(_PyObject_VAR_SIZE(tp, size)); if (op == NULL) return (PyVarObject *)PyErr_NoMemory(); +#ifdef WITH_CYCLE_GC + if (PyType_IS_GC(tp)) + op = (PyVarObject *) PyObject_FROM_GC(op); +#endif return PyObject_INIT_VAR(op, tp, size); } @@ -175,9 +191,23 @@ void _PyObject_Del(op) PyObject *op; { - PyObject_FREE(op); +#ifdef WITH_CYCLE_GC + if (PyType_IS_GC(op->ob_type)) { + PyGC_Head *g = PyObject_AS_GC(op); + PyObject_FREE(g); + } else +#endif + { + PyObject_FREE(op); + } } +#ifndef WITH_CYCLE_GC +/* extension modules might need these */ +void _PyGC_Insert(PyObject *op) { } +void _PyGC_Remove(PyObject *op) { } +#endif + int PyObject_Print(op, fp, flags) PyObject *op; @@ -917,8 +947,10 @@ _Py_Dealloc(op) { destructor dealloc = op->ob_type->tp_dealloc; _Py_ForgetReference(op); +#ifndef WITH_CYCLE_GC if (_PyTrash_delete_nesting < PyTrash_UNWIND_LEVEL-1) op->ob_type = NULL; +#endif (*dealloc)(op); } diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 73e33045d0e..46e67a8e032 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -94,7 +94,7 @@ PyTuple_New(size) /* Check for overflow */ if (nbytes / sizeof(PyObject *) != (size_t)size || (nbytes += sizeof(PyTupleObject) - sizeof(PyObject *) - + PyGC_INFO_SIZE) + + PyGC_HEAD_SIZE) <= 0) { return PyErr_NoMemory(); @@ -103,7 +103,7 @@ PyTuple_New(size) op = (PyTupleObject *) PyObject_MALLOC(nbytes); if (op == NULL) return PyErr_NoMemory(); - + op = (PyTupleObject *) PyObject_FROM_GC(op); PyObject_INIT_VAR(op, &PyTuple_Type, size); } for (i = 0; i < size; i++) @@ -115,6 +115,7 @@ PyTuple_New(size) Py_INCREF(op); /* extra INCREF so that this is never freed */ } #endif + PyObject_GC_Init(op); return (PyObject *) op; } @@ -181,6 +182,7 @@ tupledealloc(op) register int i; register int len = op->ob_size; Py_TRASHCAN_SAFE_BEGIN(op) + PyObject_GC_Fini(op); if (len > 0) { i = len; while (--i >= 0) @@ -453,7 +455,7 @@ PyTypeObject PyTuple_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "tuple", - sizeof(PyTupleObject) - sizeof(PyObject *) + PyGC_INFO_SIZE, + sizeof(PyTupleObject) - sizeof(PyObject *) + PyGC_HEAD_SIZE, sizeof(PyObject *), (destructor)tupledealloc, /*tp_dealloc*/ (printfunc)tupleprint, /*tp_print*/ @@ -557,12 +559,27 @@ _PyTuple_Resize(pv, newsize, last_is_sticky) } else #endif { +#ifdef WITH_CYCLE_GC + PyGC_Head *g = PyObject_AS_GC((PyObject *)v); + PyObject_GC_Fini((PyObject *)v); + sv = (PyTupleObject *) + PyObject_REALLOC((char *)g, sizeof(PyTupleObject) + + PyGC_HEAD_SIZE + + newsize * sizeof(PyObject *)); + if (g == NULL) { + sv = NULL; + } else { + sv = (PyTupleObject *)PyObject_FROM_GC(g); + } +#else sv = (PyTupleObject *) PyObject_REALLOC((char *)v, sizeof(PyTupleObject) - + PyGC_INFO_SIZE + + PyGC_HEAD_SIZE + newsize * sizeof(PyObject *)); +#endif *pv = (PyObject *) sv; if (sv == NULL) { + PyObject_GC_Init((PyObject *)v); PyObject_DEL(v); PyErr_NoMemory(); return -1; @@ -578,6 +595,7 @@ _PyTuple_Resize(pv, newsize, last_is_sticky) sv->ob_item[i - sizediff] = NULL; } } + PyObject_GC_Init(sv); sv->ob_size = newsize; return 0; } diff --git a/PC/config.c b/PC/config.c index f295f856e58..0ff4c93d131 100644 --- a/PC/config.c +++ b/PC/config.c @@ -43,6 +43,9 @@ extern void initbinascii(); #endif extern void initcmath(); extern void initerrno(); +#ifdef WITH_CYCLE_GC +extern void initgc(); +#endif #ifndef MS_WIN64 extern void initimageop(); #endif @@ -89,6 +92,9 @@ struct _inittab _PyImport_Inittab[] = { #endif {"cmath", initcmath}, {"errno", initerrno}, +#ifdef WITH_CYCLE_GC + {"gc", initgc}, +#endif #ifndef MS_WIN64 {"imageop", initimageop}, #endif diff --git a/PC/config.h b/PC/config.h index dfe51184275..43bb35f137b 100644 --- a/PC/config.h +++ b/PC/config.h @@ -451,6 +451,9 @@ typedef long intptr_t; /* Define if you want to use the GNU readline library */ /* #define WITH_READLINE 1 */ +/* Define if you want cycle garbage collection */ +/* #define WITH_CYCLE_GC 1 */ + /* Define if you have clock. */ /* #define HAVE_CLOCK */ diff --git a/PCbuild/python16.dsp b/PCbuild/python16.dsp index 4c753023983..34ec9ef4d64 100644 --- a/PCbuild/python16.dsp +++ b/PCbuild/python16.dsp @@ -659,6 +659,20 @@ SOURCE=..\Objects\funcobject.c # End Source File # Begin Source File +SOURCE=..\Modules\gcmodule.c + +!IF "$(CFG)" == "python16 - Win32 Release" + +!ELSEIF "$(CFG)" == "python16 - Win32 Debug" + +!ELSEIF "$(CFG)" == "python16 - Win32 Alpha Debug" + +!ELSEIF "$(CFG)" == "python16 - Win32 Alpha Release" + +!ENDIF + +# End Source File +# Begin Source File SOURCE=..\Python\getargs.c