[Patches] GC patch 4 (gc enabled)
Neil Schemenauer
nascheme@enme.ucalgary.ca
Wed, 14 Jun 2000 14:24:10 -0600
--vtzGhvizbBRQ85DL
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
This final GC patch adds the changes necessary for enabled GC. I
expect this patch to be the most controversial. However, all the
changes to the interpreter are surrounded by ifdefs (except for a
PyObject_GC_DEL change that I missed). The only new ABIs
introduced are _PyGC_Insert and _PyGC_Remove. Extension modules
compiled with GC enabled will need these functions (even if they
don't do anything). New APIs were introduced in the last
patches.
I am abusing Setup.thread to automaticly include the gc module
when GC is enabled. Perhaps the gc module should go somewhere
else.
Neil
--vtzGhvizbBRQ85DL
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="gc.diff"
Index: nogc.1/config.h.in
*** nogc.1/config.h.in Mon, 05 Jun 2000 00:51:28 -0600 nas (python/3_config.h.i 1.1.2.2 644)
--- gc.14(w)/config.h.in Wed, 14 Jun 2000 11:43:34 -0600 nas (python/3_config.h.i 1.1.2.3 644)
***************
*** 210,215 ****
--- 210,218 ----
(shared library plus accessory files). */
#undef WITH_NEXT_FRAMEWORK
+ /* Define if you want cycle garbage collection */
+ #undef WITH_CYCLE_GC
+
/* The number of bytes in an off_t. */
#undef SIZEOF_OFF_T
Index: nogc.1/configure.in
*** nogc.1/configure.in Mon, 05 Jun 2000 00:51:28 -0600 nas (python/5_configure. 1.1.2.2 644)
--- gc.14(w)/configure.in Wed, 14 Jun 2000 11:43:34 -0600 nas (python/5_configure. 1.1.2.3 644)
***************
*** 1083,1088 ****
--- 1083,1099 ----
fi],
[AC_MSG_RESULT(no)])
+ # Check for GC support
+ AC_SUBST(USE_GC_MODULE)
+ USE_GC_MODULE="#"
+ AC_MSG_CHECKING(for --with-cycle-gc)
+ AC_ARG_WITH(cycle-gc, [--with-cycle-gc enable garbage collection], [
+ AC_MSG_RESULT($withval)
+ AC_DEFINE(WITH_CYCLE_GC)
+ USE_GC_MODULE=
+ ],
+ AC_MSG_RESULT(no))
+
# THIS MUST BE LAST, IT CAN BREAK OTHER TESTS!
# Add sys/socket.h to confdefs.h
cat >> confdefs.h <<\EOF
Index: nogc.1/Include/objimpl.h
*** nogc.1/Include/objimpl.h Wed, 14 Jun 2000 13:39:10 -0600 nas (python/o/19_objimpl.h 1.1.2.1.1.2 644)
--- gc.14(w)/Include/objimpl.h Wed, 14 Jun 2000 14:06:01 -0600 nas (python/o/19_objimpl.h 1.1.2.4 644)
***************
*** 256,262 ****
#define PyObject_GC_Init(op)
#define PyObject_GC_Fini(op)
! #endif
#ifdef __cplusplus
}
--- 256,300 ----
#define PyObject_GC_Init(op)
#define PyObject_GC_Fini(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(op)
! #define PyObject_GC_Fini(op) _PyGC_Remove(op)
!
! /* Structure *prefixed* to container objects participating in GC */
! typedef struct _gcinfo {
! struct _gcinfo *gc_next;
! struct _gcinfo *gc_prev;
! int gc_refs;
! } PyGCInfo;
!
! #define PyGC_INFO_SIZE sizeof(PyGCInfo)
!
! /* Test if a type has GC info */
! #define PyGC_TYPE_HAS_INFO(t) PyType_HasFeature((t), Py_TPFLAGS_GC)
!
! /* Test if an object has GC info */
! #define PyGC_HAS_INFO(o) PyGC_TYPE_HAS_INFO((o)->ob_type)
!
! /* Get an object's GC info -- NULL if the object has none */
! #define PyGC_INFO(o) (PyGC_HAS_INFO(o) ? ((PyGCInfo *)(o)-1) : NULL)
!
! /* Unsafe version of PyGC_INFO() -- only call if PyGC_HAS_INFO(p) is true */
! #define PyGC_INFO_UNSAFE(o) ((PyGCInfo *)(o)-1)
!
! /* Get the object given the PyGCInfo */
! #define PyGC_OBJ(g) ((PyObject *)((g)+1))
!
! /* Interpreter macro, use PyObject_Del for extension types. */
! #define PyObject_GC_DEL(op) PyObject_FREE(PyGC_INFO(op))
!
! #endif /* WITH_CYCLE_GC */
#ifdef __cplusplus
}
Index: nogc.1/Modules/Setup.thread.in
*** nogc.1/Modules/Setup.thread.in Tue, 25 Apr 2000 17:33:19 -0600 nas (python/C/19_Setup.thre 1.1 644)
--- gc.14(w)/Modules/Setup.thread.in Wed, 14 Jun 2000 11:43:34 -0600 nas (python/C/19_Setup.thre 1.2 644)
***************
*** 9,11 ****
--- 9,14 ----
# support threads.
@USE_THREAD_MODULE@thread threadmodule.c
+
+ # Garbage collection enabled with --with-cycle-gc
+ @USE_GC_MODULE@gc gcmodule.c
Index: nogc.1/Objects/classobject.c
*** nogc.1/Objects/classobject.c Wed, 14 Jun 2000 13:39:10 -0600 nas (python/E/16_classobjec 1.1.2.2.1.3 644)
--- gc.14(w)/Objects/classobject.c Wed, 14 Jun 2000 13:44:26 -0600 nas (python/E/16_classobjec 1.1.2.6 644)
***************
*** 578,584 ****
--- 578,586 ----
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);
***************
*** 1738,1743 ****
while (free_list) {
PyMethodObject *im = free_list;
free_list = (PyMethodObject *)(im->im_self);
! PyObject_DEL(im);
}
}
--- 1740,1745 ----
while (free_list) {
PyMethodObject *im = free_list;
free_list = (PyMethodObject *)(im->im_self);
! PyObject_GC_DEL(im);
}
}
Index: nogc.1/Objects/listobject.c
*** nogc.1/Objects/listobject.c Wed, 14 Jun 2000 13:39:10 -0600 nas (python/E/25_listobject 1.1.2.3.1.3 644)
--- gc.14(w)/Objects/listobject.c Wed, 14 Jun 2000 11:43:34 -0600 nas (python/E/25_listobject 1.1.2.5 644)
***************
*** 76,81 ****
--- 76,84 ----
if (op == NULL) {
return PyErr_NoMemory();
}
+ #ifdef WITH_CYCLE_GC
+ op = (PyListObject *) PyGC_OBJ((PyGCInfo *)op);
+ #endif
if (size <= 0) {
op->ob_item = NULL;
}
Index: nogc.1/Objects/object.c
*** nogc.1/Objects/object.c Wed, 14 Jun 2000 12:25:30 -0600 nas (python/E/29_object.c 1.1.1.1.1.1 644)
--- gc.14(w)/Objects/object.c Wed, 14 Jun 2000 12:33:09 -0600 nas (python/E/29_object.c 1.1.1.1.1.1 644)
***************
*** 122,127 ****
--- 122,131 ----
"NULL object passed to PyObject_Init");
return op;
}
+ #ifdef WITH_CYCLE_GC
+ if (PyGC_TYPE_HAS_INFO(tp))
+ op = (PyObject *) PyGC_OBJ((PyGCInfo *)op);
+ #endif
/* Any changes should be reflected in PyObject_INIT (objimpl.h) */
op->ob_type = tp;
_Py_NewReference(op);
***************
*** 139,144 ****
--- 143,152 ----
"NULL object passed to PyObject_InitVar");
return op;
}
+ #ifdef WITH_CYCLE_GC
+ if (PyGC_TYPE_HAS_INFO(tp))
+ op = (PyVarObject *) PyGC_OBJ((PyGCInfo *)op);
+ #endif
/* Any changes should be reflected in PyObject_INIT_VAR */
op->ob_size = size;
op->ob_type = tp;
***************
*** 154,159 ****
--- 162,171 ----
op = (PyObject *) PyObject_MALLOC(_PyObject_SIZE(tp));
if (op == NULL)
return PyErr_NoMemory();
+ #ifdef WITH_CYCLE_GC
+ if (PyGC_TYPE_HAS_INFO(tp))
+ op = (PyObject *) PyGC_OBJ((PyGCInfo *)op);
+ #endif
return PyObject_INIT(op, tp);
}
***************
*** 166,171 ****
--- 178,187 ----
op = (PyVarObject *) PyObject_MALLOC(_PyObject_VAR_SIZE(tp, size));
if (op == NULL)
return (PyVarObject *)PyErr_NoMemory();
+ #ifdef WITH_CYCLE_GC
+ if (PyGC_TYPE_HAS_INFO(tp))
+ op = (PyVarObject *) PyGC_OBJ((PyGCInfo *)op);
+ #endif
return PyObject_INIT_VAR(op, tp, size);
}
***************
*** 173,181 ****
_PyObject_Del(op)
PyObject *op;
{
! PyObject_FREE(op);
}
int
PyObject_Print(op, fp, flags)
PyObject *op;
--- 189,211 ----
_PyObject_Del(op)
PyObject *op;
{
! #ifdef WITH_CYCLE_GC
! if (PyGC_TYPE_HAS_INFO(op->ob_type)) {
! PyGCInfo *g = PyGC_INFO(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;
***************
*** 846,853 ****
--- 876,885 ----
{
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);
}
Index: nogc.1/Objects/tupleobject.c
*** nogc.1/Objects/tupleobject.c Wed, 14 Jun 2000 13:39:10 -0600 nas (python/E/33_tupleobjec 1.1.2.3.1.3 644)
--- gc.14(w)/Objects/tupleobject.c Wed, 14 Jun 2000 13:48:47 -0600 nas (python/E/33_tupleobjec 1.1.2.5 644)
***************
*** 103,108 ****
--- 103,111 ----
op = (PyTupleObject *) PyObject_MALLOC(nbytes);
if (op == NULL)
return PyErr_NoMemory();
+ #ifdef WITH_CYCLE_GC
+ op = (PyTupleObject *) PyGC_OBJ((PyGCInfo *)op);
+ #endif
PyObject_INIT_VAR(op, &PyTuple_Type, size);
}
for (i = 0; i < size; i++)
***************
*** 555,564 ****
--- 558,581 ----
} else
#endif
{
+ #ifdef WITH_CYCLE_GC
+ PyGCInfo *g = PyGC_INFO((PyObject *)v);
+ PyObject_GC_Fini((PyObject *)v);
+ sv = (PyTupleObject *)
+ PyObject_REALLOC((char *)g, sizeof(PyTupleObject)
+ + PyGC_INFO_SIZE
+ + newsize * sizeof(PyObject *));
+ if (g == NULL) {
+ sv = NULL;
+ } else {
+ sv = (PyTupleObject *)PyGC_OBJ(g);
+ }
+ #else
sv = (PyTupleObject *)
PyObject_REALLOC((char *)v, sizeof(PyTupleObject)
+ PyGC_INFO_SIZE
+ newsize * sizeof(PyObject *));
+ #endif
*pv = (PyObject *) sv;
if (sv == NULL) {
PyObject_GC_Init((PyObject *)v);
Index: nogc.1/PC/config.c
*** nogc.1/PC/config.c Mon, 08 May 2000 12:24:03 -0600 nas (python/E/39_config.c 1.1.2.1 644)
--- gc.14(w)/PC/config.c Wed, 14 Jun 2000 11:43:34 -0600 nas (python/E/39_config.c 1.1.2.2 644)
***************
*** 43,48 ****
--- 43,51 ----
#endif
extern void initcmath();
extern void initerrno();
+ #ifdef WITH_CYCLE_GC
+ extern void initgc();
+ #endif
#ifndef MS_WIN64
extern void initimageop();
#endif
***************
*** 89,94 ****
--- 92,100 ----
#endif
{"cmath", initcmath},
{"errno", initerrno},
+ #ifdef WITH_CYCLE_GC
+ {"gc", initgc},
+ #endif
#ifndef MS_WIN64
{"imageop", initimageop},
#endif
Index: nogc.1/PC/config.h
*** nogc.1/PC/config.h Mon, 05 Jun 2000 00:51:28 -0600 nas (python/E/40_config.h 1.1.2.2 644)
--- gc.14(w)/PC/config.h Wed, 14 Jun 2000 13:50:56 -0600 nas (python/E/40_config.h 1.1.2.3 644)
***************
*** 421,426 ****
--- 421,429 ----
/* 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 */
Index: nogc.1/PCbuild/python16.dsp
*** nogc.1/PCbuild/python16.dsp Wed, 07 Jun 2000 13:08:39 -0600 nas (python/G/1_python16.d 1.1.1.1.1.1 644)
--- gc.14(w)/PCbuild/python16.dsp Wed, 14 Jun 2000 11:43:34 -0600 nas (python/G/1_python16.d 1.1.1.1.1.2 644)
***************
*** 660,665 ****
--- 660,680 ----
# 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
!IF "$(CFG)" == "python16 - Win32 Release"
Index: nogc.1/Modules/gcmodule.c
*** nogc.1/Modules/gcmodule.c Wed, 14 Jun 2000 14:06:03 -0600 nas ()
--- gc.14(w)/Modules/gcmodule.c Wed, 14 Jun 2000 14:01:04 -0600 nas (python/M/10_gcmodule.c 1.6 644)
***************
*** 0 ****
--- 1,675 ----
+ /*
+
+ Reference Cycle Garbage Collection
+ ==================================
+
+ Neil Schemenauer <nascheme@enme.ucalgary.ca>
+
+ 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 PyGCInfo generation0 = {&generation0, &generation0, 0};
+ static PyGCInfo generation1 = {&generation1, &generation1, 0};
+ static PyGCInfo 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(PyGCInfo *list)
+ {
+ list->gc_prev = list;
+ list->gc_next = list;
+ }
+
+ static void
+ gc_list_append(PyGCInfo *node, PyGCInfo *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(PyGCInfo *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(PyGCInfo *from, PyGCInfo *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(PyGCInfo *from, PyGCInfo *to)
+ {
+ PyGCInfo *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(PyGCInfo *list)
+ {
+ PyGCInfo *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(PyGCInfo *containers)
+ {
+ PyGCInfo *gc = containers->gc_next;
+ for (; gc != containers; gc=gc->gc_next) {
+ gc->gc_refs = PyGC_OBJ(gc)->ob_refcnt;
+ }
+ }
+
+ static int
+ visit_decref(PyObject *op, void *data)
+ {
+ if (op && PyGC_HAS_INFO(op)) {
+ PyGC_INFO_UNSAFE(op)->gc_refs--;
+ }
+ return 1;
+ }
+
+ /* Subtract internal references from gc_refs */
+ static void
+ subtract_refs(PyGCInfo *containers)
+ {
+ recurseproc recurse;
+ PyGCInfo *gc = containers->gc_next;
+ for (; gc != containers; gc=gc->gc_next) {
+ recurse = PyGC_OBJ(gc)->ob_type->tp_recurse;
+ (void) recurse(PyGC_OBJ(gc),
+ (visitproc)visit_decref,
+ NULL);
+ }
+ }
+
+ /* Append objects with gc_refs > 0 to roots list */
+ static void
+ move_roots(PyGCInfo *containers, PyGCInfo *roots)
+ {
+ PyGCInfo *next;
+ PyGCInfo *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, PyGCInfo *roots)
+ {
+ PyGCInfo *gc = PyGC_INFO(op);
+ if (gc && gc->gc_refs != GC_MOVED) {
+ gc_list_remove(gc);
+ gc_list_append(gc, roots);
+ gc->gc_refs = GC_MOVED;
+ }
+ return 1;
+ }
+
+ /* Move objects referenced from reachable to reachable set. */
+ static void
+ move_root_reachable(PyGCInfo *reachable)
+ {
+ recurseproc recurse;
+ PyGCInfo *gc = reachable->gc_next;
+ for (; gc != reachable; gc=gc->gc_next) {
+ /* careful, reachable list is growing here */
+ PyObject *op = PyGC_OBJ(gc);
+ recurse = op->ob_type->tp_recurse;
+ (void) recurse(op,
+ (visitproc)visit_reachable,
+ (void *)reachable);
+ }
+ }
+
+ /* move all objects with finalizers (instances with __del__) */
+ static void
+ move_finalizers(PyGCInfo *unreachable, PyGCInfo *finalizers)
+ {
+ PyGCInfo *next;
+ PyGCInfo *gc = unreachable->gc_next;
+ static PyObject *delstr;
+ if (delstr == NULL) {
+ delstr = PyString_InternFromString("__del__");
+ }
+ for (; gc != unreachable; gc=next) {
+ PyObject *op = PyGC_OBJ(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_recurse */
+ static int
+ visit_finalizer_reachable(PyObject *op, PyGCInfo *finalizers)
+ {
+ PyGCInfo *gc = PyGC_INFO(op);
+ if (gc && gc->gc_refs != GC_MOVED) {
+ gc_list_remove(gc);
+ gc_list_append(gc, finalizers);
+ gc->gc_refs = GC_MOVED;
+ }
+ return 1;
+ }
+
+ /* Move objects referenced from roots to roots */
+ static void
+ move_finalizer_reachable(PyGCInfo *finalizers)
+ {
+ recurseproc recurse;
+ PyGCInfo *gc = finalizers->gc_next;
+ for (; gc != finalizers; gc=gc->gc_next) {
+ /* careful, finalizers list is growing here */
+ recurse = PyGC_OBJ(gc)->ob_type->tp_recurse;
+ (void) recurse(PyGC_OBJ(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(PyGCInfo *finalizers, PyGCInfo *old)
+ {
+ PyGCInfo *gc;
+ if (garbage == NULL) {
+ garbage = PyList_New(0);
+ }
+ for (gc = finalizers->gc_next; gc != finalizers;
+ gc = finalizers->gc_next) {
+ PyObject *op = PyGC_OBJ(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(PyGCInfo *unreachable, PyGCInfo *old)
+ {
+ inquiry clear;
+
+ while (unreachable->gc_next != unreachable) {
+ PyGCInfo *gc = unreachable->gc_next;
+ PyObject *op = PyGC_OBJ(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(PyGCInfo *young, PyGCInfo *old)
+ {
+ long n = 0;
+ long m = 0;
+ PyGCInfo reachable;
+ PyGCInfo unreachable;
+ PyGCInfo finalizers;
+ PyGCInfo *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 ", PyGC_OBJ(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 ", PyGC_OBJ(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 (!PyGC_HAS_INFO(op)) {
+ abort();
+ }
+ #endif
+ if (threshold0 && allocated > threshold0 && !collecting) {
+ collecting++;
+ collect_generations();
+ collecting--;
+ }
+ allocated++;
+ gc_list_append(PyGC_INFO(op), &generation0);
+ }
+
+ void
+ _PyGC_Remove(PyObject *op)
+ {
+ PyGCInfo *g = PyGC_INFO(op);
+ #ifdef Py_DEBUG
+ if (!PyGC_HAS_INFO(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 */
Index: nogc.1/Lib/test/test_gc.py
*** nogc.1/Lib/test/test_gc.py Wed, 14 Jun 2000 14:06:03 -0600 nas ()
--- gc.14(w)/Lib/test/test_gc.py Wed, 14 Jun 2000 11:43:34 -0600 nas (python/M/12_test_gc.py 1.2 644)
***************
*** 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()
--vtzGhvizbBRQ85DL--