[prev in list] [next in list] [prev in thread] [next in thread]
List: python-patches
Subject: [Patches] Reference cycle collection for Python
From: nascheme () enme ! ucalgary ! ca (nascheme () enme ! ucalgary ! ca)
Date: 2000-02-29 22:34:21
Message-ID: 20000229153421.A16502 () acs ! ucalgary ! ca
[Download RAW message or body]
Here is my latest patch to add reference cycle collection to
Python. This patch adds a new configure option called
--with-cycle-gc and a new module called gc.
Comments welcome.
Neil (aka nas)
diff -rcN Python-cvs/Include/Python.h Python-gc/Include/Python.h
*** Python-cvs/Include/Python.h Wed Feb 9 04:38:15 2000
--- Python-gc/Include/Python.h Tue Feb 29 15:09:59 2000
***************
*** 72,77 ****
--- 72,81 ----
#include "pydebug.h"
+ #ifdef WITH_CYCLE_GC
+ #include "cycle_gc.h"
+ #endif
+
#include "intobject.h"
#include "longobject.h"
#include "floatobject.h"
diff -rcN Python-cvs/Include/cycle_gc.h Python-gc/Include/cycle_gc.h
*** Python-cvs/Include/cycle_gc.h Wed Dec 31 17:00:00 1969
--- Python-gc/Include/cycle_gc.h Tue Feb 29 15:09:59 2000
***************
*** 0 ****
--- 1,19 ----
+ #ifndef Py_CYCLE_GC_H
+ #define Py_CYCLE_GC_H
+
+ struct _gc_state; /* Forward */
+
+ #define PyGCObject_HEAD \
+ PyObject_HEAD \
+ struct _gc_linked *gc_next; \
+ struct _gc_linked *gc_prev;
+
+ typedef struct _gc_linked {
+ PyGCObject_HEAD
+ } PyGCObject;
+
+ struct _gc_state *PyGC_Initialize(void);
+ int PyGC_Insert(PyGCObject *op);
+ void PyGC_Remove(PyGCObject *op);
+
+ #endif
diff -rcN Python-cvs/Include/object.h Python-gc/Include/object.h
*** Python-cvs/Include/object.h Tue Feb 29 14:55:02 2000
--- Python-gc/Include/object.h Tue Feb 29 15:09:59 2000
***************
*** 212,217 ****
--- 212,221 ----
typedef int (*cmpfunc) Py_PROTO((PyObject *, PyObject *));
typedef PyObject *(*reprfunc) Py_PROTO((PyObject *));
typedef long (*hashfunc) Py_PROTO((PyObject *));
+ #ifdef WITH_CYCLE_GC
+ typedef int (*accountfunc) Py_PROTO((PyObject *, void *));
+ typedef int (*recursefunc) Py_PROTO((PyObject *, accountfunc, void *));
+ #endif
typedef struct _typeobject {
PyObject_VAR_HEAD
***************
*** 249,256 ****
char *tp_doc; /* Documentation string */
! /* More spares */
long tp_xxx5;
long tp_xxx6;
long tp_xxx7;
long tp_xxx8;
--- 253,266 ----
char *tp_doc; /* Documentation string */
! #ifdef WITH_CYCLE_GC
! /* call function for all accessible objects */
! recursefunc tp_recurse;
! #else
long tp_xxx5;
+ #endif
+
+ /* More spares */
long tp_xxx6;
long tp_xxx7;
long tp_xxx8;
diff -rcN Python-cvs/Include/pystate.h Python-gc/Include/pystate.h
*** Python-cvs/Include/pystate.h Mon Dec 21 13:21:19 1998
--- Python-gc/Include/pystate.h Tue Feb 29 15:09:59 2000
***************
*** 54,59 ****
--- 54,63 ----
int checkinterval;
+ #ifdef WITH_CYCLE_GC
+ struct _gc_state *gc_state;
+ #endif
+
} PyInterpreterState;
diff -rcN Python-cvs/Modules/Setup.in Python-gc/Modules/Setup.in
*** Python-cvs/Modules/Setup.in Tue Feb 29 14:55:08 2000
--- Python-gc/Modules/Setup.in Tue Feb 29 15:09:59 2000
***************
*** 94,99 ****
--- 94,101 ----
posix posixmodule.c # posix (UNIX) system calls
signal signalmodule.c # signal(2)
+ #gc gcmodule.c # cycle garbage collection
+
# The SGI specific GL module:
GLHACK=-Dclear=__GLclear
diff -rcN Python-cvs/Modules/gcmodule.c Python-gc/Modules/gcmodule.c
*** Python-cvs/Modules/gcmodule.c Wed Dec 31 17:00:00 1969
--- Python-gc/Modules/gcmodule.c Tue Feb 29 15:26:55 2000
***************
*** 0 ****
--- 1,820 ----
+ /*
+
+ Reference Cycle Garbage Collection
+ ==================================
+
+ Neil Schemenauer <nascheme@enme.ucalgary.ca>
+
+ Based on a patch by Toby Kelsey and ideas from Guido van Rossum and Tim
+ Peters.
+
+ This garbage collector uses local information to determine inaccessible
+ cycles.
+
+ How this method differs from mark-and-sweep
+ -------------------------------------------
+
+ Mark and sweep works by first keeping a list of all alive objects, then
+ starting from base (directly referenced) objects, recursively marking
+ all objects which are referenced by them. Any alive objects that are
+ not marked after this are inaccessible and are deleted. Consequently
+ 1) it requires a global list of objects
+ 2) it must find all base references
+ 3) it must access all reachable objects during the sweep phase.
+ There are problems with this method in locating all relevant objects
+ in the system, especially when libraries are used.
+
+ This method does not require such global analysis. Instead it finds
+ sets of objects that are only refered to by objects within that set.
+ Since the base references are not included, objects in these sets are
+ not accessible from the base references and are therefore garbage.
+
+ The first phase then consists of maintaining a set of objects that can
+ possibly be involved in reference cycles.
+
+ The second phase, collection, which may be activated periodically, at
+ user choice or when the set of watched objects is large enough, is to
+ check the objects in the set and determine from local evidence whether
+ they are inaccessible.
+
+ The first step is for each object in the set of watched objects,
+ determine the objects reachable from it. The second step is to
+ determine, for each object in this new set, whether any of its
+ references are due to objects not in the set. If there are no
+ external references, the set is isolated and can be deleted.
+
+ This is not sufficient to find all inaccessible groups, however.
+ Consider the link structure: A<=>B -> E<=>F <- C<=>D
+ The set will contain A (or B) and C (or D).
+ But
+ get_accessible(A or B) gives [A,B,E,F], and F has an external link
+ while
+ get_accessible(C or D) gives [C,D,E,F], and E has an external link
+ so this group would always escape deletion.
+
+ One option is to ignore this type of group and accept some leakage.
+ A better option is to remove from the group all items accessible
+ externally. In this example, remove [E,F] to leave [A,B] or [C,D] resp.
+ The remaining group objects are not externally accessible and can be
+ deleted.
+
+ The second phase thus consists firstly of checking all objects in the
+ set of watched objects and finding all reachable objects; secondly
+ finding all external references and removing all objects reachable from
+ them; and finally deleting any remaining objects, which are
+ inaccessible.
+
+ This implementation currently only considers cycles involving dictionary
+ objects. Also, only objects that define tp_recurse can be involved in
+ collected cycles.
+
+ TODO:
+ make thread safe (okay already?)
+
+ IDEAS:
+ use dummy element at the head of the lists
+ change debug flag to use bits
+ include module automaticly if WITH_CYCLE_GC defined
+ command line options
+
+ */
+
+
+ #include "Python.h"
+
+ #ifndef WITH_CYCLE_GC
+ #error "You must define WITH_CYCLE_GC to include this module"
+ #endif
+
+ #ifdef WITH_THREAD
+ #error "This module is not yet thread safe. Compile without threads."
+ #endif
+
+ /* #define DEBUG */
+
+ /* ==== begin hash table implementation ================================== */
+
+ /*
+ * hash table implementation is stolen from dictobject.c. Look there for
+ * comments.
+ */
+
+ #define MINSIZE 4 /* XXX make this bigger? */
+
+ static long polys[] = {
+ 4 + 3,
+ 8 + 3,
+ 16 + 3,
+ 32 + 5,
+ 64 + 3,
+ 128 + 3,
+ 256 + 29,
+ 512 + 17,
+ 1024 + 9,
+ 2048 + 5,
+ 4096 + 83,
+ 8192 + 27,
+ 16384 + 43,
+ 32768 + 3,
+ 65536 + 45,
+ 131072 + 9,
+ 262144 + 39,
+ 524288 + 39,
+ 1048576 + 9,
+ 2097152 + 5,
+ 4194304 + 3,
+ 8388608 + 33,
+ 16777216 + 27,
+ 33554432 + 9,
+ 67108864 + 71,
+ 134217728 + 39,
+ 268435456 + 9,
+ 536870912 + 5,
+ 1073741824 + 83,
+ 0
+ };
+
+ #define DUMMY -9999999 /* value for deleted entries */
+
+ /* empty: key is NULL
+ * deleted: key is not NULL, value is DUMMY
+ * used: key is not NULL, value is not DUMMY
+ */
+ typedef struct {
+ PyObject *key;
+ int value;
+ } hashentry;
+
+ typedef struct _hashtable {
+ int fill;
+ int used;
+ int size;
+ int poly;
+ hashentry *table;
+ } hashtable;
+
+
+ static hashtable *
+ hash_new()
+ {
+ register hashtable *mp;
+ mp = PyMem_NEW(hashtable, 1);
+ if (mp == NULL)
+ return NULL;
+ mp->size = 0;
+ mp->poly = 0;
+ mp->table = NULL;
+ mp->fill = 0;
+ mp->used = 0;
+ return mp;
+ }
+
+ static hashentry *
+ hash_lookup(hashtable *mp, PyObject *key)
+ {
+ register int i;
+ register long hash;
+ register unsigned incr;
+ register hashentry *freeslot;
+ register unsigned int mask = mp->size-1;
+ hashentry *ep0 = mp->table;
+ register hashentry *ep;
+
+ hash = (long)key;
+ i = (~hash) & mask;
+ ep = &ep0[i];
+ if (ep->key == NULL || ep->key == key)
+ return ep;
+ if (ep->value == DUMMY) {
+ freeslot = ep;
+ } else {
+ freeslot = NULL;
+ }
+ incr = (hash ^ ((unsigned long)hash >> 3)) & mask;
+ if (!incr)
+ incr = mask;
+ for (;;) {
+ ep = &ep0[(i+incr)&mask];
+ if (ep->key == NULL) {
+ if (freeslot != NULL)
+ return freeslot;
+ else
+ return ep;
+ }
+ if (ep->value == DUMMY && freeslot == NULL) {
+ freeslot = ep;
+ } else if (ep->key == key) {
+ return ep;
+ }
+ /* Cycle through GF(2^n)-{0} */
+ incr = incr << 1;
+ if (incr > mask)
+ incr ^= mp->poly; /* This will implicitely clear
+ the highest bit */
+ }
+ }
+
+ static int
+ hash_insert(hashtable *mp, PyObject *key, int value)
+ {
+ register hashentry *ep;
+ ep = hash_lookup(mp, key);
+ if (ep->key == key && ep->value != DUMMY) {
+ ep->value = value;
+ return 1;
+ }
+ else {
+ if (ep->key == NULL)
+ mp->fill++;
+ ep->key = key;
+ ep->value = value;
+ mp->used++;
+ return 0;
+ }
+ }
+
+ static int
+ hash_resize(hashtable *mp, int minused)
+ {
+ register int oldsize = mp->size;
+ register int newsize, newpoly;
+ register hashentry *oldtable = mp->table;
+ register hashentry *newtable;
+ register hashentry *ep;
+ register int i;
+ for (i = 0, newsize = MINSIZE; ; i++, newsize <<= 1) {
+ if (i > sizeof(polys)/sizeof(polys[0])) {
+ /* Ran out of polynomials */
+ PyErr_NoMemory();
+ return -1;
+ }
+ if (newsize > minused) {
+ newpoly = polys[i];
+ break;
+ }
+ }
+ newtable = (hashentry *) malloc(sizeof(hashentry) * newsize);
+ if (newtable == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ memset(newtable, '\0', sizeof(hashentry) * newsize);
+ mp->size = newsize;
+ mp->poly = newpoly;
+ mp->table = newtable;
+ mp->fill = 0;
+ mp->used = 0;
+
+ for (i = 0, ep = oldtable; i < oldsize; i++, ep++) {
+ if (ep->key != NULL && ep->value != DUMMY)
+ hash_insert(mp,ep->key,ep->value);
+ }
+
+ PyMem_XDEL(oldtable);
+ return 0;
+ }
+
+ static int
+ hash_setitem(hashtable *mp, PyObject *key, int value)
+ {
+ /* if fill >= 2/3 size, double in size */
+ if (mp->fill*3 >= mp->size*2) {
+ if (hash_resize(mp, mp->used*2) != 0) {
+ if (mp->fill+1 > mp->size)
+ return -1;
+ }
+ }
+ return hash_insert(mp, key, value);
+ }
+
+ static int
+ hash_delitem(hashtable *mp, PyObject *key)
+ {
+ register hashentry *ep;
+
+ if (mp->table == NULL)
+ goto empty;
+ ep = hash_lookup(mp, key);
+ if (ep->key == NULL || ep->value == DUMMY) {
+ empty:
+ return 0;
+ }
+ ep->value = DUMMY;
+ mp->used--;
+ return 1;
+ }
+
+ static void
+ hash_dealloc(hashtable *mp)
+ {
+ PyMem_XDEL(mp->table);
+ PyMem_DEL(mp);
+ }
+
+ static PyObject *
+ hash_next(hashtable *mp, int *ppos, int *value)
+ {
+ int i;
+ i = *ppos;
+ if (i < 0)
+ return NULL;
+ while (i < mp->size && (mp->table[i].key == NULL ||
+ mp->table[i].value == DUMMY))
+ i++;
+ *ppos = i+1;
+ if (i >= mp->size)
+ return NULL;
+
+ if (value)
+ *value = mp->table[i].value;
+
+ return mp->table[i].key;
+ }
+
+ /* ==== end hash table implementation ================================== */
+
+
+ typedef struct _gc_state {
+ PyGCObject *pcr; /* young "interesting" objects */
+ PyGCObject *pcr_old; /* old "interesting" objects */
+ hashtable *dying; /* none NULL if collecting */
+ int threshold; /* collection frequency */
+ int threshold_old; /* old collection frequency */
+ int allocated; /* objects allocated since collection */
+ int aged; /* objects aged since old collection */
+ int debug; /* set to > 0 to debug cycles */
+ } gc_state;
+
+
+ #define GC_STATE (_PyThreadState_Current ? \
+ _PyThreadState_Current->interp->gc_state : NULL)
+
+ gc_state *
+ PyGC_Initialize(void)
+ {
+ gc_state *gc;
+
+ gc = PyMem_NEW(gc_state, 1);
+ if (!gc) {
+ return NULL;
+ }
+ gc->pcr = NULL;
+ gc->pcr_old = NULL;
+ /* set threshold to zero to disable collection */
+ gc->threshold = 25;
+ gc->threshold_old = 250;
+ gc->allocated = 0;
+ gc->aged = 0;
+ #ifdef DEBUG
+ gc->debug = 2;
+ #else
+ gc->debug = 0;
+ #endif
+ return gc;
+ }
+
+ #ifdef DEBUG
+ static void
+ print_table(hashtable *t)
+ {
+ int i;
+ int value;
+ PyObject *op;
+
+ i = 0;
+ while ((op = hash_next(t, &i, &value))) {
+ printf("0x%lx => %d\n", (long)op, value);
+ }
+ }
+ #endif
+
+ static int
+ decref(PyObject *op, hashtable *t)
+ {
+ hashentry *ep;
+
+ ep = hash_lookup(t, op);
+ if (ep->key != NULL && ep->value != DUMMY) {
+ ep->value--;
+ }
+ return 1;
+ }
+
+ /*
+ * Recursively walk over objects accessible from op and add to t.
+ */
+ static int
+ walk_add(PyObject *op, hashtable *t)
+ {
+ int rv;
+ recursefunc recurse;
+
+ rv = hash_setitem(t, op, op->ob_refcnt);
+ if (rv == -1) {
+ PyErr_SetString(PyExc_MemoryError,
+ "Out of memory for garbage collection");
+ return 0; /* error */
+ }
+ if (rv) {
+ return 1; /* already in table */
+ } else {
+ if ((recurse = op->ob_type->tp_recurse) != NULL) {
+ return recurse(op, (accountfunc)walk_add, (void *)t);
+ } else {
+ return 1; /* not walkable */
+ }
+ }
+ }
+
+ /*
+ * Recursively walk over objects accessible from op and remove from t.
+ */
+ static int
+ walk_remove(PyObject *op, hashtable *t)
+ {
+ recursefunc recurse;
+
+ if (!hash_delitem(t, op)) {
+ return 1; /* not in table */
+ }
+ if ((recurse = op->ob_type->tp_recurse) != NULL) {
+ return recurse(op, (accountfunc)walk_remove, (void *)t);
+ } else {
+ return 1; /* not walkable */
+ }
+ }
+
+ /*
+ * Find all objects accessible from items in pcr.
+ */
+ static int
+ add_reachable(hashtable *reachable, PyGCObject *op)
+ {
+ for (; op != NULL; op=op->gc_next) {
+ if (!walk_add((PyObject *)op, reachable)) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+ /*
+ * Subtract internal link-count off stored ref-count.
+ */
+ static void
+ subtract_refcounts(hashtable *reachable)
+ {
+ PyObject *op;
+ recursefunc recurse;
+ int i = 0;
+
+ while ((op = hash_next(reachable, &i, NULL))) {
+ if ((recurse = op->ob_type->tp_recurse) != NULL) {
+ (void) recurse(op, (accountfunc)decref,
+ (void *)reachable);
+ }
+ }
+ }
+
+ /*
+ * Remove objects accessible externally (non-zero refcnt).
+ */
+ static void
+ remove_external(hashtable *reachable)
+ {
+ int i;
+ PyObject *op;
+ int refcnt;
+
+ i = 0;
+ while ((op = hash_next(reachable, &i, &refcnt))) {
+ if (refcnt > 0) {
+ /* has external references */
+ walk_remove(op, reachable);
+ }
+ }
+ }
+
+ /* be careful not to create new dictionaries */
+ static void
+ debug_instance(PyInstanceObject *inst)
+ {
+ PyObject *classname = inst->in_class->cl_name;
+ char *cname;
+ if (classname != NULL && PyString_Check(classname))
+ cname = PyString_AsString(classname);
+ else
+ cname = "?";
+ PyErr_Clear();
+ fprintf(stderr, "gc: <%.100s instance at %lx>\n", cname, (long)inst);
+ }
+
+ static void
+ debug_object(PyObject *op)
+ {
+ PyObject *repr;
+
+ repr = PyObject_Repr(op);
+ fprintf(stderr, "gc: %s\n", PyString_AsString(repr));
+ Py_DECREF(repr);
+ }
+
+ static void
+ debug_cycles(hashtable *reachable)
+ {
+ PyObject *op;
+ int i;
+
+ i = 0;
+ while ((op = hash_next(reachable, &i, NULL))) {
+ if (PyInstance_Check(op)) {
+ debug_instance((PyInstanceObject *)op);
+ } else if (PyClass_Check(op) || PyModule_Check(op)) {
+ debug_object(op);
+ }
+ }
+ }
+
+ /*
+ * Effectively deletes objects in cycles by breaking links between them.
+ * Breaking links may invalidate other objects in cycle, these are removed
+ * from reachable automatically via PyGC_Remove().
+ */
+ static void
+ delete_garbage(hashtable *reachable)
+ {
+ PyObject *op;
+ int i;
+ gc_state *gc = GC_STATE;
+
+ gc->dying = reachable;
+
+ if (gc->debug >= 2) {
+ debug_cycles(reachable);
+ }
+ i = 0;
+ while ((op = hash_next(reachable, &i, NULL))) {
+ if (PyDict_Check(op)) {
+ Py_INCREF(op);
+ /* XXX just delete things in reachable? */
+ PyDict_Clear(op);
+ Py_DECREF(op);
+ }
+ }
+
+ gc->dying = NULL;
+ }
+
+ static long
+ collect(PyGCObject *pcr, PyGCObject *pcr_old)
+ {
+ long nd;
+ hashtable *reachable = hash_new();
+
+ if (!reachable) {
+ PyErr_SetString(PyExc_MemoryError, "Out of memory for GC");
+ return -1;
+ }
+
+ /* Collect reachable objects from pcr, noting refcounts */
+ if (!add_reachable(reachable, pcr)) {
+ return -1;
+ }
+ if (!add_reachable(reachable, pcr_old)) {
+ return -1;
+ }
+
+ /* Subtract off internal ref-counts */
+ subtract_refcounts(reachable);
+
+ /* Remove objects reachable from externally referenced items */
+ remove_external(reachable);
+
+
+ /* Delete remaining (inaccessible) items */
+ nd = reachable->used;
+ if (nd > 0) {
+ gc_state *gc = GC_STATE;
+ if (gc->debug) {
+ fprintf(stderr, "collection: %ld inaccessible\n",
+ nd);
+ }
+ delete_garbage(reachable);
+ }
+
+ hash_dealloc(reachable);
+
+ return nd;
+ }
+
+ static long
+ gc_collect(gc_state *gc)
+ {
+ PyGCObject *op;
+ int n;
+
+ if (gc->threshold <= 0) {
+ return 0; /* collection disabled */
+ }
+ if (gc->aged > gc->threshold_old) {
+ /* collecting old set */
+ n = collect(gc->pcr, gc->pcr_old);
+ gc->aged = 0;
+ } else {
+ n = collect(gc->pcr, NULL);
+ }
+ gc->allocated = 0;
+ if (n >= 0 && gc->pcr) {
+ /* move survivors into the old set*/
+ op = gc->pcr;
+ while (op != NULL) {
+ /* XXX could be smarter here */
+ PyGCObject *next = op->gc_next;
+ PyGCObject *tail = gc->pcr_old;
+ gc->pcr_old = op;
+ op->gc_prev = NULL;
+ op->gc_next = tail;
+ if (tail != NULL) {
+ tail->gc_prev = op;
+ }
+ gc->aged++;
+ op = next;
+ }
+ gc->pcr = NULL;
+ }
+ return n;
+ }
+
+ int
+ PyGC_Insert(PyGCObject *op)
+ {
+ gc_state *gc = GC_STATE;
+
+ if (gc->allocated++ > gc->threshold) {
+ if (gc_collect(gc) < 0) {
+ return 0;
+ }
+ }
+ op->gc_next = gc->pcr;
+ op->gc_prev = NULL;
+ if (op->gc_next != NULL) {
+ op->gc_next->gc_prev = op;
+ }
+ gc->pcr = op;
+ return 1;
+ }
+
+ void
+ PyGC_Remove(PyGCObject *op)
+ {
+ gc_state *gc = GC_STATE;
+
+ if (!gc) {
+ return; /* XXX interpreter shutting down? */
+ }
+ if (gc->dying) {
+ /* Collection is happening. We don't want to touch this object
+ * if it is in the dying set as it has already been freed. */
+ hash_delitem(gc->dying, (PyObject *)op);
+ }
+
+ if (op->gc_prev == NULL) {
+ if (op == gc->pcr) {
+ gc->pcr = op->gc_next;
+ } else {
+ gc->pcr_old = op->gc_next;
+ }
+ } else {
+ op->gc_prev->gc_next = op->gc_next;
+ }
+ if (op->gc_next != NULL) {
+ op->gc_next->gc_prev = op->gc_prev;
+ }
+ gc->allocated--;
+ }
+
+
+ static char collect__doc__[] =
+ "collect() -> n\n"
+ "\n"
+ "Run a full collection right now. The number of objects collected is\n"
+ "returned."
+ ;
+
+ static PyObject *
+ Py_collect(self, args)
+ PyObject *self;
+ PyObject *args;
+ {
+ long n;
+ gc_state *gc = GC_STATE;
+
+ if(!PyArg_ParseTuple(args, "")) /* check no args */
+ return NULL;
+
+ gc->aged = gc->threshold_old+1; /* do full collection */
+
+ n = gc_collect(gc);
+ if (n < 0) /* exception already set */
+ return NULL;
+
+ return Py_BuildValue("l", n);
+ }
+
+ static char set_debug__doc__[] =
+ "set_debug(n) -> None\n"
+ "\n"
+ "Set the current garbage collection debugging level. Debugging information\n"
+ "is printed on sys.stderr.\n"
+ "\n"
+ " n == 0 - Don't print any debugging information.\n"
+ " n >= 1 - Print number of inaccessible objects after each collection.\n"
+ " n >= 2 - Print information on objects being collected."
+ ;
+
+ static PyObject *
+ Py_set_debug(self, args)
+ PyObject *self;
+ PyObject *args;
+ {
+ gc_state *gc = GC_STATE;
+
+ if (!PyArg_ParseTuple(args, "l", &gc->debug))
+ return NULL;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ static char set_thresh__doc__[] =
+ "set_threshold(threshold, [threshold_old]) -> None\n"
+ "\n"
+ "Sets the collection thresholds. Setting threshold to zero disables\n"
+ "collection."
+ "\n"
+ "threshold - number of new dictionaries before collecting\n"
+ "threshold_old - number of dictionaries aged before collecting old set.\n"
+ "\n"
+ ;
+
+ static PyObject *
+ Py_set_thresh(self, args)
+ PyObject *self;
+ PyObject *args;
+ {
+ gc_state *gc = GC_STATE;
+
+ if (!PyArg_ParseTuple(args, "l|l", &gc->threshold, &gc->threshold_old))
+ return NULL;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ static char get_thresh__doc__[] =
+ "get_treshold() -> (threshold, threshold_old)"
+ "\n"
+ "Return the current collection thresholds"
+ ;
+
+ static PyObject *
+ Py_get_thresh(self, args)
+ PyObject *self;
+ PyObject *args;
+ {
+ gc_state *gc = GC_STATE;
+
+ if(!PyArg_ParseTuple(args, "")) /* no args */
+ return NULL;
+
+ return Py_BuildValue("(ii)", gc->threshold, gc->threshold_old);
+ }
+
+
+ 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(n) -- Set the debugging level.\n"
+ "set_threshold(threshold, [threshold_old]) -- Set the collection threshold.\n"
+ "get_threshold() -- Return the current the collection thresholds.\n"
+ ;
+
+ static PyMethodDef GcMethods[] = {
+ {"set_debug", Py_set_debug, METH_VARARGS, set_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)
+ {
+ (void) Py_InitModule4("gc",
+ GcMethods,
+ gc__doc__,
+ NULL,
+ PYTHON_API_VERSION);
+ }
+
diff -rcN Python-cvs/Objects/classobject.c Python-gc/Objects/classobject.c
*** Python-cvs/Objects/classobject.c Tue Feb 29 14:55:15 2000
--- Python-gc/Objects/classobject.c Tue Feb 29 15:09:59 2000
***************
*** 386,391 ****
--- 386,406 ----
return res;
}
+ #ifdef WITH_CYCLE_GC
+ static int
+ class_recurse(PyClassObject *cl, accountfunc func, void *data)
+ {
+ if (!(*func)(cl->cl_bases, data)) {
+ return 0;
+ }
+ if (!(*func)(cl->cl_dict, data)) {
+ return 0;
+ }
+ return 1;
+ }
+ #endif /* WITH_CYCLE_GC */
+
+
PyTypeObject PyClass_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
***************
*** 406,411 ****
--- 421,432 ----
(reprfunc)class_str, /*tp_str*/
(getattrofunc)class_getattr, /*tp_getattro*/
(setattrofunc)class_setattr, /*tp_setattro*/
+ 0, /* tp_as_buffer */
+ 0, /* tp_flags */
+ 0, /* tp_doc */
+ #ifdef WITH_CYCLE_GC
+ (recursefunc)class_recurse, /* tp_recurse */
+ #endif
};
int
***************
*** 837,842 ****
--- 858,877 ----
return outcome;
}
+ #ifdef WITH_CYCLE_GC
+ static int
+ instance_recurse(PyInstanceObject *in, accountfunc func, void *data)
+ {
+ if (!(*func)((PyObject *)in->in_class, data)) {
+ return 0;
+ }
+ if (!(*func)(in->in_dict, data)) {
+ return 0;
+ }
+ return 1;
+ }
+ #endif /* WITH_CYCLE_GC */
+
static PyObject *getitemstr, *setitemstr, *delitemstr, *lenstr;
static int
***************
*** 1461,1466 ****
--- 1496,1505 ----
(setattrofunc)instance_setattr, /*tp_setattro*/
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /*tp_flags */
+ 0, /* tp_doc */
+ #ifdef WITH_CYCLE_GC
+ (recursefunc)instance_recurse, /* tp_recurse */
+ #endif
};
diff -rcN Python-cvs/Objects/dictobject.c Python-gc/Objects/dictobject.c
*** Python-cvs/Objects/dictobject.c Tue Feb 29 14:55:15 2000
--- Python-gc/Objects/dictobject.c Tue Feb 29 15:09:59 2000
***************
*** 104,110 ****
--- 104,114 ----
when it is more than half filled.
*/
typedef struct {
+ #ifdef WITH_CYCLE_GC
+ PyGCObject_HEAD
+ #else
PyObject_HEAD
+ #endif
int ma_fill;
int ma_used;
int ma_size;
***************
*** 129,134 ****
--- 133,144 ----
mp->ma_table = NULL;
mp->ma_fill = 0;
mp->ma_used = 0;
+ #ifdef WITH_CYCLE_GC
+ if (!PyGC_Insert((PyGCObject *)mp)) {
+ Py_DECREF(mp);
+ return NULL;
+ }
+ #endif
return (PyObject *)mp;
}
***************
*** 487,492 ****
--- 497,505 ----
Py_DECREF(ep->me_value);
}
}
+ #ifdef WITH_CYCLE_GC
+ PyGC_Remove((PyGCObject *)mp);
+ #endif
PyMem_XDEL(mp->ma_table);
PyMem_DEL(mp);
}
***************
*** 1020,1025 ****
--- 1033,1058 ----
return Py_None;
}
+ #ifdef WITH_CYCLE_GC
+ static int
+ dict_recurse(PyObject *op, accountfunc func, void *data)
+ {
+ int i = 0;
+ PyObject *pk;
+ PyObject *pv;
+
+ while (PyDict_Next(op, &i, &pk, &pv)) {
+ if (!(*func)(pk, data)) {
+ return 0;
+ }
+ if (!(*func)(pv, data)) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ #endif /* WITH_CYCLE_GC */
+
static PyMethodDef mapp_methods[] = {
{"has_key", (PyCFunction)dict_has_key, METH_VARARGS},
{"keys", (PyCFunction)dict_keys},
***************
*** 1055,1060 ****
--- 1088,1104 ----
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&dict_as_mapping, /*tp_as_mapping*/
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ 0, /* tp_flags */
+ 0, /* tp_doc */
+ #ifdef WITH_CYCLE_GC
+ (recursefunc)dict_recurse, /* tp_recurse */
+ #endif
};
/* For backward compatibility with old dictionary interface */
diff -rcN Python-cvs/Objects/listobject.c Python-gc/Objects/listobject.c
*** Python-cvs/Objects/listobject.c Tue Feb 29 14:55:15 2000
--- Python-gc/Objects/listobject.c Tue Feb 29 15:09:59 2000
***************
*** 1383,1388 ****
--- 1383,1405 ----
return NULL;
}
+ #ifdef WITH_CYCLE_GC
+ static int
+ list_recurse(PyListObject *lp, accountfunc func, void *data)
+ {
+ int i;
+ if (lp->ob_item != NULL) {
+ i = lp->ob_size;
+ while (--i >= 0) {
+ if (!(*func)(lp->ob_item[i], data)) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ }
+ #endif /* WITH_CYCLE_GC */
+
static char append_doc[] =
"L.append(object) -- append object to end";
static char extend_doc[] =
***************
*** 1448,1453 ****
--- 1465,1481 ----
0, /*tp_as_number*/
&list_as_sequence, /*tp_as_sequence*/
0, /*tp_as_mapping*/
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ 0, /* tp_flags */
+ 0, /* tp_doc */
+ #ifdef WITH_CYCLE_GC
+ (recursefunc)list_recurse, /* tp_recurse */
+ #endif
};
diff -rcN Python-cvs/Objects/tupleobject.c Python-gc/Objects/tupleobject.c
*** Python-cvs/Objects/tupleobject.c Thu Jan 20 15:32:54 2000
--- Python-gc/Objects/tupleobject.c Tue Feb 29 15:09:59 2000
***************
*** 390,395 ****
--- 390,412 ----
return (PyObject *) np;
}
+ #ifdef WITH_CYCLE_GC
+ static int
+ tuplerecurse(PyTupleObject *tp, accountfunc func, void *data)
+ {
+ int i;
+ if (tp->ob_item != NULL) {
+ i = tp->ob_size;
+ while (--i >= 0) {
+ if (!(*func)(tp->ob_item[i], data)) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ }
+ #endif /* WITH_CYCLE_GC */
+
static PySequenceMethods tuple_as_sequence = {
(inquiry)tuplelength, /*sq_length*/
(binaryfunc)tupleconcat, /*sq_concat*/
***************
*** 416,421 ****
--- 433,448 ----
&tuple_as_sequence, /*tp_as_sequence*/
0, /*tp_as_mapping*/
(hashfunc)tuplehash, /*tp_hash*/
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ 0, /* tp_flags */
+ 0, /* tp_doc */
+ #ifdef WITH_CYCLE_GC
+ (recursefunc)tuplerecurse, /* tp_recurse */
+ #endif
};
/* The following function breaks the notion that tuples are immutable:
diff -rcN Python-cvs/Python/pystate.c Python-gc/Python/pystate.c
*** Python-cvs/Python/pystate.c Fri Jun 18 08:22:24 1999
--- Python-gc/Python/pystate.c Tue Feb 29 15:09:59 2000
***************
*** 63,68 ****
--- 63,75 ----
PyInterpreterState *interp = PyMem_NEW(PyInterpreterState, 1);
if (interp != NULL) {
+ #ifdef WITH_CYCLE_GC
+ interp->gc_state = PyGC_Initialize();
+ if (!interp->gc_state) {
+ PyMem_DEL(interp);
+ return NULL;
+ }
+ #endif
HEAD_INIT();
interp->modules = NULL;
interp->sysdict = NULL;
diff -rcN Python-cvs/config.h.in Python-gc/config.h.in
*** Python-cvs/config.h.in Mon Dec 20 14:25:59 1999
--- Python-gc/config.h.in Tue Feb 29 15:09:59 2000
***************
*** 185,190 ****
--- 185,193 ----
(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
diff -rcN Python-cvs/configure.in Python-gc/configure.in
*** Python-cvs/configure.in Tue Feb 29 14:55:01 2000
--- Python-gc/configure.in Tue Feb 29 15:09:59 2000
***************
*** 1025,1030 ****
--- 1025,1037 ----
AC_DEFINE(MALLOC_ZERO_RETURNS_NULL)
fi
+ AC_MSG_CHECKING(for --with-cycle-gc)
+ AC_ARG_WITH(cycle-gc, [--with-cycle-gc enable reference cycle collection], [
+ AC_MSG_RESULT($withval)
+ AC_DEFINE(WITH_CYCLE_GC)
+ ],
+ AC_MSG_RESULT(no))
+
# generate output files
AC_OUTPUT(Makefile \
Objects/Makefile \
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic