[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