[Zodb-checkins] CVS: Zope/lib/python/persistent - TimeStamp.c:1.4 __init__.py:1.6 cPersistence.c:1.74 cPersistence.h:1.27 cPickleCache.c:1.87 list.py:1.5 mapping.py:1.22 ring.c:1.2 ring.h:1.2

Jim Fulton cvs-admin at zope.org
Fri Nov 28 11:45:28 EST 2003


Update of /cvs-repository/Zope/lib/python/persistent
In directory cvs.zope.org:/tmp/cvs-serv3783/lib/python/persistent

Added Files:
	TimeStamp.c __init__.py cPersistence.c cPersistence.h 
	cPickleCache.c list.py mapping.py ring.c ring.h 
Log Message:
Merged Jeremy and Tim's changes from the zodb33-devel-branch.


=== Zope/lib/python/persistent/TimeStamp.c 1.3 => 1.4 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/TimeStamp.c	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,434 @@
+/*****************************************************************************
+
+  Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+
+  This software is subject to the provisions of the Zope Public License,
+  Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+  WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+  FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+
+#include "Python.h"
+#include <time.h>
+
+PyObject *TimeStamp_FromDate(int, int, int, int, int, double);
+PyObject *TimeStamp_FromString(const char *);
+
+static char TimeStampModule_doc[] =
+"A 64-bit TimeStamp used as a ZODB serial number.\n";
+
+typedef struct {
+    PyObject_HEAD
+    unsigned char data[8];
+} TimeStamp;
+
+/* The first dimension of the arrays below is non-leapyear / leapyear */
+
+static char month_len[2][12]={
+  {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+  {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+};
+
+static short joff[2][12] = {
+  {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
+  {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
+};
+
+static double gmoff=0;
+
+/* XXX should this be stored in sconv? */
+#define SCONV ((double)60) / ((double)(1<<16)) / ((double)(1<<16))
+
+static int
+leap(int year)
+{
+    return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+}
+
+static int
+days_in_month(int year, int month)
+{
+    return month_len[leap(year)][month];
+}
+
+static double
+TimeStamp_yad(int y)
+{
+    double d, s;
+
+    y -= 1900;
+
+    d = (y - 1) * 365;
+    if (y > 0) {
+        s = 1.0;
+	y -= 1;
+    } else {
+	s = -1.0;
+	y = -y;
+    }
+    return d + s * (y / 4 - y / 100 + (y + 300) / 400);
+}
+
+static double
+TimeStamp_abst(int y, int mo, int d, int m, int s)
+{
+    return (TimeStamp_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s;
+}
+
+static int
+TimeStamp_init_gmoff(void)
+{
+    struct tm *t;
+    time_t z=0;
+
+    t = gmtime(&z);
+    if (t == NULL) {
+	PyErr_SetString(PyExc_SystemError, "gmtime failed");
+	return -1;
+    }
+
+    gmoff = TimeStamp_abst(t->tm_year+1900, t->tm_mon, t->tm_mday - 1,
+			   t->tm_hour * 60 + t->tm_min, t->tm_sec);
+
+    return 0;
+}
+
+static void
+TimeStamp_dealloc(TimeStamp *ts)
+{
+    PyObject_Del(ts);
+}
+
+static int
+TimeStamp_compare(TimeStamp *v, TimeStamp *w)
+{
+    int cmp = memcmp(v->data, w->data, 8);
+    if (cmp < 0) return -1;
+    if (cmp > 0) return 1;
+    return 0;
+}
+
+static long
+TimeStamp_hash(TimeStamp *self)
+{
+    register unsigned char *p = (unsigned char *)self->data;
+    register int len = 8;
+    register long x = *p << 7;
+    /* XXX unroll loop? */
+    while (--len >= 0)
+	x = (1000003*x) ^ *p++;
+    x ^= 8;
+    if (x == -1)
+	x = -2;
+    return x;
+}
+
+typedef struct {
+    /* XXX reverse-engineer what's in these things and comment them */
+    int y;
+    int m;
+    int d;
+    int mi;
+} TimeStampParts;
+
+static void
+TimeStamp_unpack(TimeStamp *self, TimeStampParts *p)
+{
+    unsigned long v;
+
+    v = (self->data[0] * 16777216 + self->data[1] * 65536
+	 + self->data[2] * 256 + self->data[3]);
+    p->y = v / 535680 + 1900;
+    p->m = (v % 535680) / 44640 + 1;
+    p->d = (v % 44640) / 1440 + 1;
+    p->mi = v % 1440;
+}
+
+static double
+TimeStamp_sec(TimeStamp *self)
+{
+    unsigned int v;
+
+    v = (self->data[4] * 16777216 + self->data[5] * 65536
+	 + self->data[6] * 256 + self->data[7]);
+    return SCONV * v;
+}
+
+static PyObject *
+TimeStamp_year(TimeStamp *self)
+{
+    TimeStampParts p;
+    TimeStamp_unpack(self, &p);
+    return PyInt_FromLong(p.y);
+}
+
+static PyObject *
+TimeStamp_month(TimeStamp *self)
+{
+    TimeStampParts p;
+    TimeStamp_unpack(self, &p);
+    return PyInt_FromLong(p.m);
+}
+
+static PyObject *
+TimeStamp_day(TimeStamp *self)
+{
+    TimeStampParts p;
+    TimeStamp_unpack(self, &p);
+    return PyInt_FromLong(p.d);
+}
+
+static PyObject *
+TimeStamp_hour(TimeStamp *self)
+{
+    TimeStampParts p;
+    TimeStamp_unpack(self, &p);
+    return PyInt_FromLong(p.mi / 60);
+}
+
+static PyObject *
+TimeStamp_minute(TimeStamp *self)
+{
+    TimeStampParts p;
+    TimeStamp_unpack(self, &p);
+    return PyInt_FromLong(p.mi % 60);
+}
+
+static PyObject *
+TimeStamp_second(TimeStamp *self)
+{
+    return PyFloat_FromDouble(TimeStamp_sec(self));
+}
+
+static PyObject *
+TimeStamp_timeTime(TimeStamp *self)
+{
+    TimeStampParts p;
+    TimeStamp_unpack(self, &p);
+    return PyFloat_FromDouble(TimeStamp_abst(p.y, p.m - 1, p.d - 1, p.mi, 0)
+			      + TimeStamp_sec(self) - gmoff);
+}
+
+static PyObject *
+TimeStamp_raw(TimeStamp *self)
+{
+    return PyString_FromStringAndSize(self->data, 8);
+}
+
+static PyObject *
+TimeStamp_str(TimeStamp *self)
+{
+    char buf[128];
+    TimeStampParts p;
+    int len;
+
+    TimeStamp_unpack(self, &p);
+    len =sprintf(buf, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f",
+	         p.y, p.m, p.d, p.mi / 60, p.mi % 60,
+	         TimeStamp_sec(self));
+
+    return PyString_FromStringAndSize(buf, len);
+}
+
+
+static PyObject *
+TimeStamp_laterThan(TimeStamp *self, PyObject *obj)
+{
+    TimeStamp *o = NULL;
+    TimeStampParts p;
+    unsigned char new[8];
+    int i;
+
+    if (obj->ob_type != self->ob_type) {
+	PyErr_SetString(PyExc_TypeError, "expected TimeStamp object");
+	return NULL;
+    }
+    o = (TimeStamp *)obj;
+    if (memcmp(self->data, o->data, 8) > 0) {
+	Py_INCREF(self);
+	return (PyObject *)self;
+    }
+
+    memcpy(new, o->data, 8);
+    for (i = 7; i > 3; i--) {
+	if (new[i] == 255)
+	    new[i] = 0;
+	else {
+	    new[i]++;
+	    return TimeStamp_FromString(new);
+	}
+    }
+
+    /* All but the first two bytes are the same.  Need to increment
+       the year, month, and day explicitly. */
+    TimeStamp_unpack(o, &p);
+    if (p.mi >= 1439) {
+	p.mi = 0;
+	if (p.d == month_len[leap(p.y)][p.m - 1]) {
+	    p.d = 1;
+	    if (p.m == 12) {
+		p.m = 1;
+		p.y++;
+	    } else
+		p.m++;
+	} else
+	    p.d++;
+    } else
+	p.mi++;
+
+    return TimeStamp_FromDate(p.y, p.m, p.d, p.mi / 60, p.mi % 60, 0);
+}
+
+static struct PyMethodDef TimeStamp_methods[] = {
+    {"year", 	(PyCFunction)TimeStamp_year, 	METH_NOARGS},
+    {"minute", 	(PyCFunction)TimeStamp_minute, 	METH_NOARGS},
+    {"month", 	(PyCFunction)TimeStamp_month, 	METH_NOARGS},
+    {"day", 	(PyCFunction)TimeStamp_day,	METH_NOARGS},
+    {"hour", 	(PyCFunction)TimeStamp_hour, 	METH_NOARGS},
+    {"second", 	(PyCFunction)TimeStamp_second, 	METH_NOARGS},
+    {"timeTime",(PyCFunction)TimeStamp_timeTime, 	METH_NOARGS},
+    {"laterThan", (PyCFunction)TimeStamp_laterThan, 	METH_O},
+    {"raw",	(PyCFunction)TimeStamp_raw,	METH_NOARGS},
+    {NULL,	NULL},
+};
+
+static PyTypeObject TimeStamp_type = {
+    PyObject_HEAD_INIT(NULL)
+    0,
+    "persistent.TimeStamp",
+    sizeof(TimeStamp),
+    0,
+    (destructor)TimeStamp_dealloc,	/* tp_dealloc */
+    0,					/* tp_print */
+    0,					/* tp_getattr */
+    0,					/* tp_setattr */
+    (cmpfunc)TimeStamp_compare,		/* tp_compare */
+    (reprfunc)TimeStamp_raw,		/* tp_repr */
+    0,					/* tp_as_number */
+    0,					/* tp_as_sequence */
+    0,					/* tp_as_mapping */
+    (hashfunc)TimeStamp_hash,		/* tp_hash */
+    0,					/* tp_call */
+    (reprfunc)TimeStamp_str,		/* tp_str */
+    0,					/* tp_getattro */
+    0,					/* tp_setattro */
+    0,					/* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+    0,					/* tp_doc */
+    0,					/* tp_traverse */
+    0,					/* tp_clear */
+    0,					/* tp_richcompare */
+    0,					/* tp_weaklistoffset */
+    0,					/* tp_iter */
+    0,					/* tp_iternext */
+    TimeStamp_methods,			/* tp_methods */
+    0,					/* tp_members */
+    0,					/* tp_getset */
+    0,					/* tp_base */
+    0,					/* tp_dict */
+    0,					/* tp_descr_get */
+    0,					/* tp_descr_set */
+};
+
+PyObject *
+TimeStamp_FromString(const char *buf)
+{
+    /* buf must be exactly 8 characters */
+    TimeStamp *ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
+    memcpy(ts->data, buf, 8);
+    return (PyObject *)ts;
+}
+
+#define CHECK_RANGE(VAR, LO, HI) if ((VAR) < (LO) || (VAR) > (HI)) { \
+     return PyErr_Format(PyExc_ValueError, \
+			 # VAR " must be between %d and %d: %d", \
+			 (LO), (HI), (VAR)); \
+    }
+
+PyObject *
+TimeStamp_FromDate(int year, int month, int day, int hour, int min,
+		   double sec)
+{
+    TimeStamp *ts = NULL;
+    int d;
+    unsigned int v;
+
+    if (year < 1900)
+	return PyErr_Format(PyExc_ValueError,
+			    "year must be greater than 1900: %d", year);
+    CHECK_RANGE(month, 1, 12);
+    d = days_in_month(year, month - 1);
+    if (day < 1 || day > d)
+	return PyErr_Format(PyExc_ValueError,
+			    "day must be between 1 and %d: %d", d, day);
+    CHECK_RANGE(hour, 0, 23);
+    CHECK_RANGE(min, 0, 59);
+    /* Seconds are allowed to be anything, so chill
+       If we did want to be pickly, 60 would be a better choice.
+    if (sec < 0 || sec > 59)
+	return PyErr_Format(PyExc_ValueError,
+			    "second must be between 0 and 59: %f", sec);
+    */
+    ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
+    v = (((year - 1900) * 12 + month - 1) * 31 + day - 1);
+    v = (v * 24 + hour) * 60 + min;
+    ts->data[0] = v / 16777216;
+    ts->data[1] = (v % 16777216) / 65536;
+    ts->data[2] = (v % 65536) / 256;
+    ts->data[3] = v % 256;
+    sec /= SCONV;
+    v = (unsigned int)sec;
+    ts->data[4] = v / 16777216;
+    ts->data[5] = (v % 16777216) / 65536;
+    ts->data[6] = (v % 65536) / 256;
+    ts->data[7] = v % 256;
+
+    return (PyObject *)ts;
+}
+
+PyObject *
+TimeStamp_TimeStamp(PyObject *obj, PyObject *args)
+{
+    char *buf = NULL;
+    int len = 0, y, mo, d, h = 0, m = 0;
+    double sec = 0;
+
+    if (PyArg_ParseTuple(args, "s#:TimeStamp", &buf, &len)) {
+	if (len != 8) {
+	    PyErr_SetString(PyExc_ValueError, "8-character string expected");
+	    return NULL;
+	}
+	return TimeStamp_FromString(buf);
+    }
+    PyErr_Clear();
+
+    if (!PyArg_ParseTuple(args, "iii|iid", &y, &mo, &d, &h, &m, &sec))
+	return NULL;
+    return TimeStamp_FromDate(y, mo, d, h, m, sec);
+}
+
+static PyMethodDef TimeStampModule_functions[] = {
+    {"TimeStamp",	TimeStamp_TimeStamp,	METH_VARARGS},
+    {NULL,		NULL},
+};
+
+
+void
+initTimeStamp(void)
+{
+    PyObject *m;
+
+    if (TimeStamp_init_gmoff() < 0)
+	return;
+
+    m = Py_InitModule4("TimeStamp", TimeStampModule_functions,
+		       TimeStampModule_doc, NULL, PYTHON_API_VERSION);
+    if (m == NULL)
+	return;
+
+    TimeStamp_type.ob_type = &PyType_Type;
+    TimeStamp_type.tp_getattro = PyObject_GenericGetAttr;
+}


=== Zope/lib/python/persistent/__init__.py 1.5 => 1.6 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/__init__.py	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Provide access to Persistent and PersistentMapping."""
+
+from cPersistence import Persistent
+from cPickleCache import PickleCache


=== Zope/lib/python/persistent/cPersistence.c 1.73 => 1.74 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/cPersistence.c	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,745 @@
+/*****************************************************************************
+
+  Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+  All Rights Reserved.
+
+  This software is subject to the provisions of the Zope Public License,
+  Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+  WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+  FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+static char cPersistence_doc_string[] =
+"Defines Persistent mixin class for persistent objects.\n"
+"\n"
+"$Id$\n";
+
+#include "cPersistence.h"
+#include "structmember.h"
+
+struct ccobject_head_struct {
+    CACHE_HEAD
+};
+
+#define ASSIGN(V,E) {PyObject *__e; __e=(E); Py_XDECREF(V); (V)=__e;}
+#define UNLESS(E) if(!(E))
+#define UNLESS_ASSIGN(V,E) ASSIGN(V,E) UNLESS(V)
+
+/* Strings initialized by init_strings() below. */
+static PyObject *py_keys, *py_setstate, *py___dict__, *py_timeTime;
+static PyObject *py__p_changed, *py__p_deactivate;
+static PyObject *py___getattr__, *py___setattr__, *py___delattr__;
+static PyObject *py___getstate__;
+
+/* These two objects are initialized when the module is loaded */
+static PyObject *TimeStamp, *py_simple_new;
+
+static int
+init_strings(void)
+{
+#define INIT_STRING(S) \
+    if (!(py_ ## S = PyString_InternFromString(#S))) \
+	return -1;
+    INIT_STRING(keys);
+    INIT_STRING(setstate);
+    INIT_STRING(timeTime);
+    INIT_STRING(__dict__);
+    INIT_STRING(_p_changed);
+    INIT_STRING(_p_deactivate);
+    INIT_STRING(__getattr__);
+    INIT_STRING(__setattr__);
+    INIT_STRING(__delattr__);
+    INIT_STRING(__getstate__);
+#undef INIT_STRING
+    return 0;
+}
+
+static void ghostify(cPersistentObject*);
+
+/* Load the state of the object, unghostifying it.  Upon success, return 1.
+ * If an error occurred, re-ghostify the object and return 0.
+ */
+static int
+unghostify(cPersistentObject *self)
+{
+    if (self->state < 0 && self->jar) {
+        PyObject *r;
+
+        /* XXX Is it ever possibly to not have a cache? */
+        if (self->cache) {
+            /* Create a node in the ring for this unghostified object. */
+            self->cache->non_ghost_count++;
+	    ring_add(&self->cache->ring_home, &self->ring);
+	    Py_INCREF(self);
+        }
+	/* set state to CHANGED while setstate() call is in progress
+	   to prevent a recursive call to _PyPersist_Load().
+	*/
+        self->state = cPersistent_CHANGED_STATE;
+        /* Call the object's __setstate__() */
+	r = PyObject_CallMethod(self->jar, "setstate", "O", (PyObject *)self);
+        if (r == NULL) {
+            ghostify(self);
+            return 0;
+        }
+        self->state = cPersistent_UPTODATE_STATE;
+        Py_DECREF(r);
+    }
+    return 1;
+}
+
+/****************************************************************************/
+
+static PyTypeObject Pertype;
+
+static void
+accessed(cPersistentObject *self)
+{
+    /* Do nothing unless the object is in a cache and not a ghost. */
+    if (self->cache && self->state >= 0 && self->ring.r_next)
+	ring_move_to_head(&self->cache->ring_home, &self->ring);
+}
+
+static void
+unlink_from_ring(cPersistentObject *self)
+{
+    /* If the cache has been cleared, then a non-ghost object
+       isn't in the ring any longer.
+    */
+    if (self->ring.r_next == NULL)
+	return;
+
+    /* if we're ghostifying an object, we better have some non-ghosts */
+    assert(self->cache->non_ghost_count > 0);
+    self->cache->non_ghost_count--;
+    ring_del(&self->ring);
+}
+
+static void
+ghostify(cPersistentObject *self)
+{
+    PyObject **dictptr;
+
+    /* are we already a ghost? */
+    if (self->state == cPersistent_GHOST_STATE)
+        return;
+
+    /* XXX is it ever possible to not have a cache? */
+    if (self->cache == NULL) {
+        self->state = cPersistent_GHOST_STATE;
+        return;
+    }
+
+    /* If the cache is still active, we must unlink the object. */
+    if (self->ring.r_next) {
+	/* if we're ghostifying an object, we better have some non-ghosts */
+	assert(self->cache->non_ghost_count > 0);
+	self->cache->non_ghost_count--;
+	ring_del(&self->ring);
+    }
+    self->state = cPersistent_GHOST_STATE;
+    dictptr = _PyObject_GetDictPtr((PyObject *)self);
+    if (dictptr && *dictptr) {
+	Py_DECREF(*dictptr);
+	*dictptr = NULL;
+    }
+
+    /* We remove the reference to the just ghosted object that the ring
+     * holds.  Note that the dictionary of oids->objects has an uncounted
+     * reference, so if the ring's reference was the only one, this frees
+     * the ghost object.  Note further that the object's dealloc knows to
+     * inform the dictionary that it is going away.
+     */
+    Py_DECREF(self);
+}
+
+static int
+changed(cPersistentObject *self)
+{
+  if ((self->state == cPersistent_UPTODATE_STATE ||
+       self->state == cPersistent_STICKY_STATE)
+       && self->jar)
+    {
+	PyObject *meth, *arg, *result;
+	static PyObject *s_register;
+
+	if (s_register == NULL)
+	    s_register = PyString_InternFromString("register");
+	meth = PyObject_GetAttr((PyObject *)self->jar, s_register);
+	if (meth == NULL)
+	    return -1;
+	arg = PyTuple_New(1);
+	if (arg == NULL) {
+	    Py_DECREF(meth);
+	    return -1;
+	}
+	PyTuple_SET_ITEM(arg, 0, (PyObject *)self);
+	result = PyEval_CallObject(meth, arg);
+	PyTuple_SET_ITEM(arg, 0, NULL);
+	Py_DECREF(arg);
+	Py_DECREF(meth);
+	if (result == NULL)
+	    return -1;
+	Py_DECREF(result);
+
+	self->state = cPersistent_CHANGED_STATE;
+    }
+
+  return 0;
+}
+
+static PyObject *
+Per__p_deactivate(cPersistentObject *self)
+{
+    if (self->state == cPersistent_UPTODATE_STATE && self->jar) {
+	PyObject **dictptr = _PyObject_GetDictPtr((PyObject *)self);
+	if (dictptr && *dictptr) {
+	    Py_DECREF(*dictptr);
+	    *dictptr = NULL;
+	}
+	/* Note that we need to set to ghost state unless we are
+	   called directly. Methods that override this need to
+	   do the same! */
+	ghostify(self);
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+#include "pickle/pickle.c"
+
+
+
+
+
+/* Return the object's state, a dict or None.
+
+   If the object has no dict, it's state is None.
+   Otherwise, return a dict containing all the attributes that
+   don't start with "_v_".
+
+   The caller should not modify this dict, as it may be a reference to
+   the object's __dict__.
+*/
+
+static PyObject *
+Per__getstate__(cPersistentObject *self)
+{
+    /* XXX Should it be an error to call __getstate__() on a ghost? */
+    if (!unghostify(self))
+        return NULL;
+
+    /* XXX shouldn't we increment stickyness? */
+    return pickle___getstate__((PyObject*)self);
+}
+
+
+static struct PyMethodDef Per_methods[] = {
+  {"_p_deactivate", (PyCFunction)Per__p_deactivate, METH_NOARGS,
+   "_p_deactivate() -- Deactivate the object"},
+  {"__getstate__", (PyCFunction)Per__getstate__, METH_NOARGS,
+   pickle___getstate__doc },
+
+  PICKLE_SETSTATE_DEF 
+  PICKLE_GETNEWARGS_DEF 
+  PICKLE_REDUCE_DEF
+
+  {NULL,		NULL}		/* sentinel */
+};
+
+/* The Persistent base type provides a traverse function, but not a
+   clear function.  An instance of a Persistent subclass will have
+   its dict cleared through subtype_clear().
+
+   There is always a cycle between a persistent object and its cache.
+   When the cycle becomes unreachable, the clear function for the
+   cache will break the cycle.  Thus, the persistent object need not
+   have a clear function.  It would be complex to write a clear function
+   for the objects, if we needed one, because of the reference count
+   tricks done by the cache.
+*/
+
+static void
+Per_dealloc(cPersistentObject *self)
+{
+    if (self->state >= 0)
+	unlink_from_ring(self);
+    if (self->cache)
+	cPersistenceCAPI->percachedel(self->cache, self->oid);
+    Py_XDECREF(self->cache);
+    Py_XDECREF(self->jar);
+    Py_XDECREF(self->oid);
+    self->ob_type->tp_free(self);
+}
+
+static int
+Per_traverse(cPersistentObject *self, visitproc visit, void *arg)
+{
+    int err;
+
+#define VISIT(SLOT) \
+    if (SLOT) { \
+	err = visit((PyObject *)(SLOT), arg); \
+	if (err) \
+		     return err; \
+    }
+
+    VISIT(self->jar);
+    VISIT(self->oid);
+    VISIT(self->cache);
+
+#undef VISIT
+    return 0;
+}
+
+/* convert_name() returns a new reference to a string name
+   or sets an exception and returns NULL.
+*/
+
+static PyObject *
+convert_name(PyObject *name)
+{
+#ifdef Py_USING_UNICODE
+    /* The Unicode to string conversion is done here because the
+       existing tp_setattro slots expect a string object as name
+       and we wouldn't want to break those. */
+    if (PyUnicode_Check(name)) {
+	name = PyUnicode_AsEncodedString(name, NULL, NULL);
+    }
+    else
+#endif
+    if (!PyString_Check(name)) {
+	PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
+	return NULL;
+    } else
+	Py_INCREF(name);
+    return name;
+}
+
+/* Returns true if the object requires unghostification.
+
+   There are several special attributes that we allow access to without
+   requiring that the object be unghostified:
+   __class__
+   __del__
+   __dict__
+   __of__
+   __setstate__
+*/
+
+static int
+unghost_getattr(const char *s)
+{
+    if (*s++ != '_')
+	return 1;
+    if (*s == 'p') {
+	s++;
+	if (*s == '_')
+	    return 0; /* _p_ */
+	else
+	    return 1;
+    }
+    else if (*s == '_') {
+	s++;
+	switch (*s) {
+	case 'c':
+	    return strcmp(s, "class__");
+	case 'd':
+	    s++;
+	    if (!strcmp(s, "el__"))
+		return 0; /* __del__ */
+	    if (!strcmp(s, "ict__"))
+		return 0; /* __dict__ */
+	    return 1;
+	case 'o':
+	    return strcmp(s, "of__");
+	case 's':
+	    return strcmp(s, "setstate__");
+	default:
+	    return 1;
+	}
+    }
+    return 1;
+}
+
+static PyObject*
+Per_getattro(cPersistentObject *self, PyObject *name)
+{
+    PyObject *result = NULL;	/* guilty until proved innocent */
+    char *s;
+
+    name = convert_name(name);
+    if (!name)
+	goto Done;
+    s = PyString_AS_STRING(name);
+
+    if (*s != '_' || unghost_getattr(s)) {
+	if (!unghostify(self))
+	    goto Done;
+	accessed(self);
+    }
+    result = PyObject_GenericGetAttr((PyObject *)self, name);
+
+  Done:
+    Py_XDECREF(name);
+    return result;
+}
+
+/* We need to decide on a reasonable way for a programmer to write
+   an __setattr__() or __delattr__() hook.
+
+   The ZODB3 has been that if you write a hook, it will be called if
+   the attribute is not an _p_ attribute and after doing any necessary
+   unghostifying.  AMK's guide says modification will not be tracked
+   automatically, so the hook must explicitly set _p_changed; I'm not
+   sure if I believe that.
+
+   This approach won't work with new-style classes, because type will
+   install a slot wrapper that calls the derived class's __setattr__().
+   That means Persistent's tp_setattro doesn't get a chance to be called.
+   Changing this behavior would require a metaclass.
+
+   One option for ZODB 3.3 is to require setattr hooks to know about
+   _p_ and to call a prep function before modifying the object's state.
+   That's the solution I like best at the moment.
+*/
+
+static int
+Per_setattro(cPersistentObject *self, PyObject *name, PyObject *v)
+{
+    int result = -1;	/* guilty until proved innocent */
+    char *s;
+
+    name = convert_name(name);
+    if (!name)
+	goto Done;
+    s = PyString_AS_STRING(name);
+
+    if (strncmp(s, "_p_", 3) != 0) {
+	if (!unghostify(self))
+	    goto Done;
+	accessed(self);
+	if (strncmp(s, "_v_", 3) != 0
+	    && self->state != cPersistent_CHANGED_STATE) {
+	    if (changed(self) < 0)
+		goto Done;
+	}
+    }
+    result = PyObject_GenericSetAttr((PyObject *)self, name, v);
+
+ Done:
+    Py_XDECREF(name);
+    return result;
+}
+
+static PyObject *
+Per_get_changed(cPersistentObject *self)
+{
+    if (self->state < 0) {
+	Py_INCREF(Py_None);
+	return Py_None;
+    }
+    return PyInt_FromLong(self->state == cPersistent_CHANGED_STATE);
+}
+
+static int
+Per_set_changed(cPersistentObject *self, PyObject *v)
+{
+    int deactivate = 0, true;
+    if (!v) {
+	/* delattr is used to invalidate an object even if it has changed. */
+	if (self->state != cPersistent_GHOST_STATE)
+	    self->state = cPersistent_UPTODATE_STATE;
+	deactivate = 1;
+    }
+    else if (v == Py_None)
+	deactivate = 1;
+
+    if (deactivate) {
+	PyObject *res, *meth;
+	meth = PyObject_GetAttr((PyObject *)self, py__p_deactivate);
+	if (meth == NULL)
+	    return -1;
+	res = PyObject_CallObject(meth, NULL);
+	if (res)
+	    Py_DECREF(res);
+	else {
+	    /* an error occured in _p_deactivate().
+
+	    It's not clear what we should do here.  The code is
+	    obviously ignoring the exception, but it shouldn't return
+	    0 for a getattr and set an exception.  The simplest change
+	    is to clear the exception, but that simply masks the
+	    error.
+
+	    XXX We'll print an error to stderr just like exceptions in
+	    __del__().  It would probably be better to log it but that
+	    would be painful from C.
+	    */
+	    PyErr_WriteUnraisable(meth);
+	}
+	Py_DECREF(meth);
+	return 0;
+    }
+    true = PyObject_IsTrue(v);
+    if (true == -1)
+	return -1;
+    else if (true)
+	return changed(self);
+
+    if (self->state >= 0)
+	self->state = cPersistent_UPTODATE_STATE;
+    return 0;
+}
+
+static PyObject *
+Per_get_oid(cPersistentObject *self)
+{
+    PyObject *oid = self->oid ? self->oid : Py_None;
+    Py_INCREF(oid);
+    return oid;
+}
+
+static int
+Per_set_oid(cPersistentObject *self, PyObject *v)
+{
+    if (self->cache) {
+	int result;
+
+	if (v == NULL) {
+	    PyErr_SetString(PyExc_ValueError,
+			    "can't delete _p_oid of cached object");
+	    return -1;
+	}
+	if (PyObject_Cmp(self->oid, v, &result) < 0)
+	    return -1;
+	if (result) {
+	    PyErr_SetString(PyExc_ValueError,
+			    "can not change _p_oid of cached object");
+	    return -1;
+	}
+    }
+    Py_XDECREF(self->oid);
+    Py_XINCREF(v);
+    self->oid = v;
+    return 0;
+}
+
+static PyObject *
+Per_get_jar(cPersistentObject *self)
+{
+    PyObject *jar = self->jar ? self->jar : Py_None;
+    Py_INCREF(jar);
+    return jar;
+}
+
+static int
+Per_set_jar(cPersistentObject *self, PyObject *v)
+{
+    if (self->cache) {
+	int result;
+
+	if (v == NULL) {
+	    PyErr_SetString(PyExc_ValueError,
+			    "can't delete _p_jar of cached object");
+	    return -1;
+	}
+	if (PyObject_Cmp(self->jar, v, &result) < 0)
+	    return -1;
+	if (result) {
+	    PyErr_SetString(PyExc_ValueError,
+			    "can not change _p_jar of cached object");
+	    return -1;
+	}
+    }
+    Py_XDECREF(self->jar);
+    Py_XINCREF(v);
+    self->jar = v;
+    return 0;
+}
+
+static PyObject *
+Per_get_serial(cPersistentObject *self)
+{
+    return PyString_FromStringAndSize(self->serial, 8);
+}
+
+static int
+Per_set_serial(cPersistentObject *self, PyObject *v)
+{
+    if (v) {
+	if (PyString_Check(v) && PyString_GET_SIZE(v) == 8)
+	    memcpy(self->serial, PyString_AS_STRING(v), 8);
+	else {
+	    PyErr_SetString(PyExc_ValueError,
+			    "_p_serial must be an 8-character string");
+	    return -1;
+	}
+    } else
+	memset(self->serial, 0, 8);
+    return 0;
+}
+
+static PyObject *
+Per_get_mtime(cPersistentObject *self)
+{
+    PyObject *t, *v;
+
+    if (!unghostify(self))
+	return NULL;
+
+    accessed(self);
+
+    if (memcmp(self->serial, "\0\0\0\0\0\0\0\0", 8) == 0) {
+	Py_INCREF(Py_None);
+	return Py_None;
+    }
+
+    t = PyObject_CallFunction(TimeStamp, "s#", self->serial, 8);
+    if (!t)
+	return NULL;
+    v = PyObject_CallMethod(t, "timeTime", "");
+    Py_DECREF(t);
+    return v;
+}
+
+static PyGetSetDef Per_getsets[] = {
+    {"_p_changed", (getter)Per_get_changed, (setter)Per_set_changed},
+    {"_p_jar", (getter)Per_get_jar, (setter)Per_set_jar},
+    {"_p_mtime", (getter)Per_get_mtime},
+    {"_p_oid", (getter)Per_get_oid, (setter)Per_set_oid},
+    {"_p_serial", (getter)Per_get_serial, (setter)Per_set_serial},
+    {NULL}
+};
+
+/* This module is compiled as a shared library.  Some compilers don't
+   allow addresses of Python objects defined in other libraries to be
+   used in static initializers here.  The DEFERRED_ADDRESS macro is
+   used to tag the slots where such addresses appear; the module init
+   function must fill in the tagged slots at runtime.  The argument is
+   for documentation -- the macro ignores it.
+*/
+#define DEFERRED_ADDRESS(ADDR) 0
+
+static PyTypeObject Pertype = {
+    PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyPersist_MetaType))
+    0,					/* ob_size */
+    "persistent.Persistent",		/* tp_name */
+    sizeof(cPersistentObject),		/* tp_basicsize */
+    0,					/* tp_itemsize */
+    (destructor)Per_dealloc,		/* tp_dealloc */
+    0,					/* tp_print */
+    0,					/* tp_getattr */
+    0,					/* tp_setattr */
+    0,					/* tp_compare */
+    0,					/* tp_repr */
+    0,					/* tp_as_number */
+    0,					/* tp_as_sequence */
+    0,					/* tp_as_mapping */
+    0,					/* tp_hash */
+    0,					/* tp_call */
+    0,					/* tp_str */
+    (getattrofunc)Per_getattro,		/* tp_getattro */
+    (setattrofunc)Per_setattro,		/* tp_setattro */
+    0,					/* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+    					/* tp_flags */
+    0,					/* tp_doc */
+    (traverseproc)Per_traverse,		/* tp_traverse */
+    0,					/* tp_clear */
+    0,					/* tp_richcompare */
+    0,					/* tp_weaklistoffset */
+    0,					/* tp_iter */
+    0,					/* tp_iternext */
+    Per_methods,			/* tp_methods */
+    0,					/* tp_members */
+    Per_getsets,			/* tp_getset */
+};
+
+/* End of code for Persistent objects */
+/* -------------------------------------------------------- */
+
+typedef int (*intfunctionwithpythonarg)(PyObject*);
+
+/* Load the object's state if necessary and become sticky */
+static int
+Per_setstate(cPersistentObject *self)
+{
+    if (!unghostify(self))
+        return -1;
+    self->state = cPersistent_STICKY_STATE;
+    return 0;
+}
+
+static PyObject *
+simple_new(PyObject *self, PyObject *type_object)
+{
+    return PyType_GenericNew((PyTypeObject *)type_object, NULL, NULL);
+}
+
+static PyMethodDef cPersistence_methods[] = {
+    {"simple_new", simple_new, METH_O,
+     "Create an object by simply calling a class's __new__ method without "
+     "arguments."},
+    {NULL, NULL}
+};
+
+
+static cPersistenceCAPIstruct
+truecPersistenceCAPI = {
+    &Pertype,
+    (getattrofunc)Per_getattro,	/*tp_getattr with object key*/
+    (setattrofunc)Per_setattro,	/*tp_setattr with object key*/
+    changed,
+    accessed,
+    ghostify,
+    (intfunctionwithpythonarg)Per_setstate,
+    NULL /* The percachedel slot is initialized in cPickleCache.c when
+	    the module is loaded.  It uses a function in a different
+	    shared library. */
+};
+
+void
+initcPersistence(void)
+{
+    PyObject *m, *s;
+
+    if (pickle_setup() < 0)
+      return;
+
+    if (init_strings() < 0)
+      return;
+
+    m = Py_InitModule3("cPersistence", cPersistence_methods,
+		       cPersistence_doc_string);
+
+    Pertype.ob_type = &PyType_Type;
+    Pertype.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&Pertype) < 0)
+	return;
+    if (PyModule_AddObject(m, "Persistent", (PyObject *)&Pertype) < 0)
+	return;
+
+    cPersistenceCAPI = &truecPersistenceCAPI;
+    s = PyCObject_FromVoidPtr(cPersistenceCAPI, NULL);
+    if (!s)
+	return;
+    if (PyModule_AddObject(m, "CAPI", s) < 0)
+	return;
+
+    py_simple_new = PyObject_GetAttrString(m, "simple_new");
+    if (!py_simple_new)
+        return;
+
+    m = PyImport_ImportModule("persistent.TimeStamp");
+    if (!m)
+	return;
+    TimeStamp = PyObject_GetAttrString(m, "TimeStamp");
+    if (!TimeStamp)
+	return;
+    Py_DECREF(m);
+}


=== Zope/lib/python/persistent/cPersistence.h 1.26 => 1.27 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/cPersistence.h	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,88 @@
+/*****************************************************************************
+
+  Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+  All Rights Reserved.
+
+  This software is subject to the provisions of the Zope Public License,
+  Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+  WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+  FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+
+#ifndef CPERSISTENCE_H
+#define CPERSISTENCE_H
+
+#include "Python.h"
+#include "ring.h"
+
+#define CACHE_HEAD \
+    PyObject_HEAD \
+    CPersistentRing ring_home; \
+    int non_ghost_count;
+
+struct ccobject_head_struct;
+
+typedef struct ccobject_head_struct PerCache;
+
+#define cPersistent_HEAD \
+    PyObject_HEAD \
+    PyObject *jar; \
+    PyObject *oid; \
+    PerCache *cache; \
+    CPersistentRing ring; \
+    char serial[8]; \
+    signed char state; \
+    unsigned char reserved[3];
+
+#define cPersistent_GHOST_STATE -1
+#define cPersistent_UPTODATE_STATE 0
+#define cPersistent_CHANGED_STATE 1
+#define cPersistent_STICKY_STATE 2
+
+typedef struct {
+    cPersistent_HEAD
+} cPersistentObject;
+
+typedef void (*percachedelfunc)(PerCache *, PyObject *);
+
+typedef struct {
+    PyTypeObject *pertype;
+    getattrofunc getattro;
+    setattrofunc setattro;
+    int (*changed)(cPersistentObject*);
+    void (*accessed)(cPersistentObject*);
+    void (*ghostify)(cPersistentObject*);
+    int (*setstate)(PyObject*);
+    percachedelfunc percachedel;
+} cPersistenceCAPIstruct;
+
+#ifndef DONT_USE_CPERSISTENCECAPI
+static cPersistenceCAPIstruct *cPersistenceCAPI;
+#endif
+
+#define cPersistanceModuleName "cPersistence"
+
+#define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype)
+
+#define PER_USE_OR_RETURN(O,R) {if((O)->state==cPersistent_GHOST_STATE && cPersistenceCAPI->setstate((PyObject*)(O)) < 0) return (R); else if ((O)->state==cPersistent_UPTODATE_STATE) (O)->state=cPersistent_STICKY_STATE;}
+
+#define PER_CHANGED(O) (cPersistenceCAPI->changed((cPersistentObject*)(O)))
+
+#define PER_GHOSTIFY(O) (cPersistenceCAPI->ghostify((cPersistentObject*)(O)))
+
+#define PER_ALLOW_DEACTIVATION(O) ((O)->state==cPersistent_STICKY_STATE && ((O)->state=cPersistent_UPTODATE_STATE))
+
+#define PER_PREVENT_DEACTIVATION(O)  ((O)->state==cPersistent_UPTODATE_STATE && ((O)->state=cPersistent_STICKY_STATE))
+
+#define PER_USE(O) \
+(((O)->state != cPersistent_GHOST_STATE \
+  || (cPersistenceCAPI->setstate((PyObject*)(O)) >= 0)) \
+ ? (((O)->state==cPersistent_UPTODATE_STATE) \
+    ? ((O)->state=cPersistent_STICKY_STATE) : 1) : 0)
+
+#define PER_ACCESSED(O)  (cPersistenceCAPI->accessed((cPersistentObject*)(O)))
+
+#endif


=== Zope/lib/python/persistent/cPickleCache.c 1.86 => 1.87 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/cPickleCache.c	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,995 @@
+/*****************************************************************************
+
+  Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+  All Rights Reserved.
+
+  This software is subject to the provisions of the Zope Public License,
+  Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+  WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+  FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+
+/*
+
+Objects are stored under three different regimes:
+
+Regime 1: Persistent Classes
+
+Persistent Classes are part of ZClasses. They are stored in the
+self->data dictionary, and are never garbage collected.
+
+The klass_items() method returns a sequence of (oid,object) tuples for
+every Persistent Class, which should make it possible to implement
+garbage collection in Python if necessary.
+
+Regime 2: Ghost Objects
+
+There is no benefit to keeping a ghost object which has no external
+references, therefore a weak reference scheme is used to ensure that
+ghost objects are removed from memory as soon as possible, when the
+last external reference is lost.
+
+Ghost objects are stored in the self->data dictionary. Normally a
+dictionary keeps a strong reference on its values, however this
+reference count is 'stolen'.
+
+This weak reference scheme leaves a dangling reference, in the
+dictionary, when the last external reference is lost. To clean up this
+dangling reference the persistent object dealloc function calls
+self->cache->_oid_unreferenced(self->oid). The cache looks up the oid
+in the dictionary, ensures it points to an object whose reference
+count is zero, then removes it from the dictionary. Before removing
+the object from the dictionary it must temporarily resurrect the
+object in much the same way that class instances are resurrected
+before their __del__ is called.
+
+Since ghost objects are stored under a different regime to non-ghost
+objects, an extra ghostify function in cPersistenceAPI replaces
+self->state=GHOST_STATE assignments that were common in other
+persistent classes (such as BTrees).
+
+Regime 3: Non-Ghost Objects
+
+Non-ghost objects are stored in two data structures: the dictionary
+mapping oids to objects and a doubly-linked list that encodes the
+order in which the objects were accessed.  The dictionary reference is
+borrowed, as it is for ghosts.  The list reference is a new reference;
+the list stores recently used objects, even if they are otherwise
+unreferenced, to avoid loading the object from the database again.
+
+The doubly-link-list nodes contain next and previous pointers linking
+together the cache and all non-ghost persistent objects.
+
+The node embedded in the cache is the home position. On every
+attribute access a non-ghost object will relink itself just behind the
+home position in the ring. Objects accessed least recently will
+eventually find themselves positioned after the home position.
+
+Occasionally other nodes are temporarily inserted in the ring as
+position markers. The cache contains a ring_lock flag which must be
+set and unset before and after doing so. Only if the flag is unset can
+the cache assume that all nodes are either his own home node, or nodes
+from persistent objects. This assumption is useful during the garbage
+collection process.
+
+The number of non-ghost objects is counted in self->non_ghost_count.
+The garbage collection process consists of traversing the ring, and
+deactivating (that is, turning into a ghost) every object until
+self->non_ghost_count is down to the target size, or until it
+reaches the home position again.
+
+Note that objects in the sticky or changed states are still kept in
+the ring, however they can not be deactivated. The garbage collection
+process must skip such objects, rather than deactivating them.
+
+*/
+
+static char cPickleCache_doc_string[] =
+"Defines the PickleCache used by ZODB Connection objects.\n"
+"\n"
+"$Id$\n";
+
+#define DONT_USE_CPERSISTENCECAPI
+#include "cPersistence.h"
+#include "structmember.h"
+#include <time.h>
+#include <stddef.h>
+#undef Py_FindMethod
+
+static PyObject *py__p_oid, *py_reload, *py__p_jar, *py__p_changed;
+static cPersistenceCAPIstruct *capi;
+
+/* This object is the pickle cache.  The CACHE_HEAD macro guarantees
+   that layout of this struct is the same as the start of
+   ccobject_head in cPersistence.c */
+typedef struct {
+    CACHE_HEAD
+    int klass_count;                         /* count of persistent classes */
+    PyObject *data;                          /* oid -> object dict */
+    PyObject *jar;                           /* Connection object */
+    PyObject *setklassstate;                 /* ??? */
+    int cache_size;                          /* target number of items in cache */
+
+    /* Most of the time the ring contains only:
+       * many nodes corresponding to persistent objects
+       * one 'home' node from the cache.
+    In some cases it is handy to temporarily add other types
+    of node into the ring as placeholders. 'ring_lock' is a boolean
+    indicating that someone has already done this. Currently this
+    is only used by the garbage collection code. */
+
+    int ring_lock;
+
+    /* 'cache_drain_resistance' controls how quickly the cache size will drop
+    when it is smaller than the configured size. A value of zero means it will
+    not drop below the configured size (suitable for most caches). Otherwise,
+    it will remove cache_non_ghost_count/cache_drain_resistance items from
+    the cache every time (suitable for rarely used caches, such as those
+    associated with Zope versions. */
+
+    int cache_drain_resistance;
+
+} ccobject;
+
+static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v);
+
+/* ---------------------------------------------------------------- */
+
+#define OBJECT_FROM_RING(SELF, HERE, CTX) \
+    ((cPersistentObject *)(((char *)here) - offsetof(cPersistentObject, ring)))
+
+static int
+scan_gc_items(ccobject *self,int target)
+{
+    /* This function must only be called with the ring lock held,
+       because it places a non-object placeholder in the ring.
+    */
+
+    cPersistentObject *object;
+    int error;
+    CPersistentRing placeholder;
+    CPersistentRing *here = self->ring_home.r_next;
+
+    /* Scan through the ring until we either find the ring_home (i.e. start
+     * of the ring, or we've ghosted enough objects to reach the target
+     * size.
+     */
+    while (1) {
+	/* back to the home position. stop looking */
+        if (here == &self->ring_home)
+            return 0;
+
+        /* At this point we know that the ring only contains nodes
+	   from persistent objects, plus our own home node. We know
+	   this because the ring lock is held.  We can safely assume
+	   the current ring node is a persistent object now we know it
+	   is not the home */
+        object = OBJECT_FROM_RING(self, here, "scan_gc_items");
+        if (!object)
+	    return -1;
+
+	/* we are small enough */
+        if (self->non_ghost_count <= target)
+            return 0;
+        else if (object->state == cPersistent_UPTODATE_STATE) {
+            /* deactivate it. This is the main memory saver. */
+
+            /* Add a placeholder; a dummy node in the ring.  We need
+	       to do this to mark our position in the ring.  It is
+	       possible that the PyObject_SetAttr() call below will
+	       invoke an __setattr__() hook in Python.  If it does,
+	       another thread might run; if that thread accesses a
+	       persistent object and moves it to the head of the ring,
+	       it might cause the gc scan to start working from the
+	       head of the list.
+	    */
+
+            placeholder.r_next = here->r_next;
+            placeholder.r_prev = here;
+            here->r_next->r_prev = &placeholder;
+            here->r_next = &placeholder;
+
+            /* In Python, "obj._p_changed = None" spells, ghostify */
+            error = PyObject_SetAttr((PyObject *)object, py__p_changed,
+				     Py_None);
+
+            /* unlink the placeholder */
+            placeholder.r_next->r_prev = placeholder.r_prev;
+            placeholder.r_prev->r_next = placeholder.r_next;
+
+            here = placeholder.r_next;
+
+            if (error)
+                return -1; /* problem */
+        }
+        else
+            here = here->r_next;
+    }
+}
+
+static PyObject *
+lockgc(ccobject *self, int target_size)
+{
+    /* This is thread-safe because of the GIL, and there's nothing
+     * in between checking the ring_lock and acquiring it that calls back
+     * into Python.
+     */
+    if (self->ring_lock) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    self->ring_lock = 1;
+    if (scan_gc_items(self, target_size)) {
+        self->ring_lock = 0;
+        return NULL;
+    }
+    self->ring_lock = 0;
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+cc_incrgc(ccobject *self, PyObject *args)
+{
+    int n = 1;
+    int starting_size = self->non_ghost_count;
+    int target_size = self->cache_size;
+
+    if (self->cache_drain_resistance >= 1) {
+        /* This cache will gradually drain down to a small size. Check
+           a (small) number of objects proportional to the current size */
+
+        int target_size_2 = (starting_size - 1
+			     - starting_size / self->cache_drain_resistance);
+        if (target_size_2 < target_size)
+            target_size = target_size_2;
+    }
+
+    if (!PyArg_ParseTuple(args, "|i:incrgc", &n))
+	return NULL;
+
+    return lockgc(self, target_size);
+}
+
+static PyObject *
+cc_full_sweep(ccobject *self, PyObject *args)
+{
+    int dt = 0;
+    if (!PyArg_ParseTuple(args, "|i:full_sweep", &dt))
+	return NULL;
+    if (dt == 0)
+	return lockgc(self, 0);
+    else
+	return cc_incrgc(self, args);
+}
+
+static PyObject *
+cc_minimize(ccobject *self, PyObject *args)
+{
+    int ignored;
+    if (!PyArg_ParseTuple(args, "|i:minimize", &ignored))
+	return NULL;
+    return lockgc(self, 0);
+}
+
+static void
+_invalidate(ccobject *self, PyObject *key)
+{
+    PyObject *v = PyDict_GetItem(self->data, key);
+
+    if (!v)
+	return;
+    if (PyType_Check(v)) {
+	if (v->ob_refcnt <= 1) {
+	    self->klass_count--;
+	    if (PyDict_DelItem(self->data, key) < 0)
+		PyErr_Clear();
+	}
+	else {
+	    v = PyObject_CallFunction(self->setklassstate, "O", v);
+	    if (v)
+		Py_DECREF(v);
+	    else
+		PyErr_Clear();
+	}
+    } else {
+	if (PyObject_DelAttr(v, py__p_changed) < 0)
+	    PyErr_Clear();
+    }
+}
+
+static PyObject *
+cc_invalidate(ccobject *self, PyObject *inv)
+{
+  PyObject *key, *v;
+  int i = 0;
+
+  if (PyDict_Check(inv)) {
+      while (PyDict_Next(inv, &i, &key, &v))
+	  _invalidate(self, key);
+      PyDict_Clear(inv);
+  }
+  else {
+      if (PyString_Check(inv))
+	  _invalidate(self, inv);
+      else {
+	  int l;
+
+	  l = PyObject_Length(inv);
+	  if (l < 0)
+	      return NULL;
+	  for (i=l; --i >= 0; ) {
+	      key = PySequence_GetItem(inv, i);
+	      if (!key)
+		  return NULL;
+	      _invalidate(self, key);
+	      Py_DECREF(key);
+	  }
+	  /* XXX Do we really want to modify the input? */
+	  PySequence_DelSlice(inv, 0, l);
+      }
+  }
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject *
+cc_get(ccobject *self, PyObject *args)
+{
+    PyObject *r, *key, *d = NULL;
+
+    if (!PyArg_ParseTuple(args, "O|O:get", &key, &d))
+	return NULL;
+
+    r = PyDict_GetItem(self->data, key);
+    if (!r) {
+	if (d) {
+	    r = d;
+	} else {
+	    PyErr_SetObject(PyExc_KeyError, key);
+	    return NULL;
+	}
+    }
+    Py_INCREF(r);
+    return r;
+}
+
+static PyObject *
+cc_items(ccobject *self)
+{
+    return PyObject_CallMethod(self->data, "items", "");
+}
+
+static PyObject *
+cc_klass_items(ccobject *self)
+{
+    PyObject *l,*k,*v;
+    int p = 0;
+
+    l = PyList_New(PyDict_Size(self->data));
+    if (l == NULL)
+	return NULL;
+
+    while (PyDict_Next(self->data, &p, &k, &v)) {
+        if(PyType_Check(v)) {
+	    v = Py_BuildValue("OO", k, v);
+	    if (v == NULL) {
+		Py_DECREF(l);
+		return NULL;
+	    }
+	    if (PyList_Append(l, v) < 0) {
+		Py_DECREF(v);
+		Py_DECREF(l);
+		return NULL;
+	    }
+	    Py_DECREF(v);
+        }
+    }
+
+    return l;
+}
+
+static PyObject *
+cc_lru_items(ccobject *self)
+{
+    PyObject *l;
+    CPersistentRing *here;
+
+    if (self->ring_lock) {
+	/* When the ring lock is held, we have no way of know which
+	   ring nodes belong to persistent objects, and which a
+	   placeholders. */
+        PyErr_SetString(PyExc_ValueError,
+		".lru_items() is unavailable during garbage collection");
+        return NULL;
+    }
+
+    l = PyList_New(0);
+    if (l == NULL)
+	return NULL;
+
+    here = self->ring_home.r_next;
+    while (here != &self->ring_home) {
+        PyObject *v;
+        cPersistentObject *object = OBJECT_FROM_RING(self, here, "cc_items");
+
+        if (object == NULL) {
+            Py_DECREF(l);
+            return NULL;
+        }
+	v = Py_BuildValue("OO", object->oid, object);
+	if (v == NULL) {
+            Py_DECREF(l);
+            return NULL;
+	}
+	if (PyList_Append(l, v) < 0) {
+	    Py_DECREF(v);
+            Py_DECREF(l);
+            return NULL;
+	}
+        Py_DECREF(v);
+        here = here->r_next;
+    }
+
+    return l;
+}
+
+static void
+cc_oid_unreferenced(ccobject *self, PyObject *oid)
+{
+    /* This is called by the persistent object deallocation function
+       when the reference count on a persistent object reaches
+       zero. We need to fix up our dictionary; its reference is now
+       dangling because we stole its reference count. Be careful to
+       not release the global interpreter lock until this is
+       complete. */
+
+    PyObject *v;
+
+    /* If the cache has been cleared by GC, data will be NULL. */
+    if (!self->data)
+	return;
+
+    v = PyDict_GetItem(self->data, oid);
+    assert(v);
+    assert(v->ob_refcnt == 0);
+    /* Need to be very hairy here because a dictionary is about
+       to decref an already deleted object.
+    */
+
+#ifdef Py_TRACE_REFS
+    /* This is called from the deallocation function after the
+       interpreter has untracked the reference.  Track it again.
+     */
+    _Py_NewReference(v);
+    /* Don't increment total refcount as a result of the
+       shenanigans played in this function.  The _Py_NewReference()
+       call above creates artificial references to v.
+    */
+    _Py_RefTotal--;
+    assert(v->ob_type);
+#else
+    Py_INCREF(v);
+#endif
+    assert(v->ob_refcnt == 1);
+    /* Incremement the refcount again, because delitem is going to
+       DECREF it.  If it's refcount reached zero again, we'd call back to
+       the dealloc function that called us.
+    */
+    Py_INCREF(v);
+
+    /* XXX Should we call _Py_ForgetReference() on error exit? */
+    if (PyDict_DelItem(self->data, oid) < 0)
+	return;
+    Py_DECREF((ccobject *)((cPersistentObject *)v)->cache);
+    ((cPersistentObject *)v)->cache = NULL;
+
+    assert(v->ob_refcnt == 1);
+
+    /* Undo the temporary resurrection.
+       Don't DECREF the object, because this function is called from
+       the object's dealloc function. If the refcnt reaches zero, it
+       will all be invoked recursively.
+     */
+    _Py_ForgetReference(v);
+}
+
+static PyObject *
+cc_ringlen(ccobject *self)
+{
+    CPersistentRing *here;
+    int c = 0;
+
+    for (here = self->ring_home.r_next; here != &self->ring_home;
+	 here = here->r_next)
+	c++;
+    return PyInt_FromLong(c);
+}
+
+static struct PyMethodDef cc_methods[] = {
+    {"items", (PyCFunction)cc_items, METH_NOARGS,
+     "Return list of oid, object pairs for all items in cache."},
+    {"lru_items", (PyCFunction)cc_lru_items, METH_NOARGS,
+     "List (oid, object) pairs from the lru list, as 2-tuples."},
+    {"klass_items", (PyCFunction)cc_klass_items, METH_NOARGS,
+     "List (oid, object) pairs of cached persistent classes."},
+    {"full_sweep", (PyCFunction)cc_full_sweep, METH_VARARGS,
+     "full_sweep([age]) -- Perform a full sweep of the cache\n\n"
+     "Supported for backwards compatibility.  If the age argument is 0,\n"
+     "behaves like minimize().  Otherwise, behaves like incrgc()."},
+    {"minimize",	(PyCFunction)cc_minimize, METH_VARARGS,
+     "minimize([ignored]) -- Remove as many objects as possible\n\n"
+     "Ghostify all objects that are not modified.  Takes an optional\n"
+     "argument, but ignores it."},
+    {"incrgc", (PyCFunction)cc_incrgc, METH_VARARGS,
+     "incrgc([n]) -- Perform incremental garbage collection\n\n"
+     "Some other implementations support an optional parameter 'n' which\n"
+     "indicates a repetition count; this value is ignored."},
+    {"invalidate", (PyCFunction)cc_invalidate, METH_O,
+     "invalidate(oids) -- invalidate one, many, or all ids"},
+    {"get", (PyCFunction)cc_get, METH_VARARGS,
+     "get(key [, default]) -- get an item, or a default"},
+    {"ringlen", (PyCFunction)cc_ringlen, METH_NOARGS,
+     "ringlen() -- Returns number of non-ghost items in cache."},
+    {NULL, NULL}		/* sentinel */
+};
+
+static int
+cc_init(ccobject *self, PyObject *args, PyObject *kwds)
+{
+    int cache_size = 100;
+    PyObject *jar;
+
+    if (!PyArg_ParseTuple(args, "O|i", &jar, &cache_size))
+	return -1;
+
+    self->setklassstate = self->jar = NULL;
+    self->data = PyDict_New();
+    if (self->data == NULL) {
+	Py_DECREF(self);
+	return -1;
+    }
+    /* Untrack the dict mapping oids to objects.
+
+    The dict contains uncounted references to ghost objects, so it
+    isn't safe for GC to visit it.  If GC finds an object with more
+    referents that refcounts, it will die with an assertion failure.
+
+    When the cache participates in GC, it will need to traverse the
+    objects in the doubly-linked list, which will account for all the
+    non-ghost objects.
+    */
+    PyObject_GC_UnTrack((void *)self->data);
+    self->setklassstate = PyObject_GetAttrString(jar, "setklassstate");
+    if (self->setklassstate == NULL) {
+	Py_DECREF(self);
+	return -1;
+    }
+    self->jar = jar;
+    Py_INCREF(jar);
+    self->cache_size = cache_size;
+    self->non_ghost_count = 0;
+    self->klass_count = 0;
+    self->cache_drain_resistance = 0;
+    self->ring_lock = 0;
+    self->ring_home.r_next = &self->ring_home;
+    self->ring_home.r_prev = &self->ring_home;
+    return 0;
+}
+
+static void
+cc_dealloc(ccobject *self)
+{
+    Py_XDECREF(self->data);
+    Py_XDECREF(self->jar);
+    Py_XDECREF(self->setklassstate);
+    PyObject_GC_Del(self);
+}
+
+static int
+cc_clear(ccobject *self)
+{
+    int pos = 0;
+    PyObject *k, *v;
+    /* Clearing the cache is delicate.
+
+    A non-ghost object will show up in the ring and in the dict.  If
+    we deallocating the dict before clearing the ring, the GC will
+    decref each object in the dict.  Since the dict references are
+    uncounted, this will lead to objects having negative refcounts.
+
+    Freeing the non-ghost objects should eliminate many objects from
+    the cache, but there may still be ghost objects left.  It's
+    not safe to decref the dict until it's empty, so we need to manually
+    clear those out of the dict, too.  We accomplish that by replacing
+    all the ghost objects with None.
+    */
+
+    /* We don't need to lock the ring, because the cache is unreachable.
+    It should be impossible for anyone to be modifying the cache.
+    */
+    assert(! self->ring_lock);
+
+    while (self->ring_home.r_next != &self->ring_home) {
+	CPersistentRing *here = self->ring_home.r_next;
+	cPersistentObject *o = OBJECT_FROM_RING(self, here, "cc_clear");
+
+	if (o->cache) {
+	    Py_INCREF(o); /* account for uncounted reference */
+	    if (PyDict_DelItem(self->data, o->oid) < 0)
+		return -1;
+	}
+	o->cache = NULL;
+	Py_DECREF(self);
+	self->ring_home.r_next = here->r_next;
+	o->ring.r_prev = NULL;
+	o->ring.r_next = NULL;
+	Py_DECREF(o);
+	here = here->r_next;
+    }
+
+    Py_XDECREF(self->jar);
+    Py_XDECREF(self->setklassstate);
+
+    while (PyDict_Next(self->data, &pos, &k, &v)) {
+	Py_INCREF(v);
+	if (PyDict_SetItem(self->data, k, Py_None) < 0)
+	    return -1;
+    }
+    Py_XDECREF(self->data);
+    self->data = NULL;
+    self->jar = NULL;
+    self->setklassstate = NULL;
+    return 0;
+}
+
+static int
+cc_traverse(ccobject *self, visitproc visit, void *arg)
+{
+    int err;
+    CPersistentRing *here;
+
+    /* If we're in the midst of cleaning up old objects, the ring contains
+     * assorted junk we must not pass on to the visit() callback.  This
+     * should be rare (our cleanup code would need to have called back
+     * into Python, which in turn triggered Python's gc).  When it happens,
+     * simply don't chase any pointers.  The cache will appear to be a
+     * source of external references then, and at worst we miss cleaning
+     * up a dead cycle until the next time Python's gc runs.
+     */
+    if (self->ring_lock)
+    	return 0;
+
+#define VISIT(SLOT) \
+    if (SLOT) { \
+	err = visit((PyObject *)(SLOT), arg); \
+	if (err) \
+		     return err; \
+    }
+
+    VISIT(self->jar);
+    VISIT(self->setklassstate);
+
+    here = self->ring_home.r_next;
+
+    /* It is possible that an object is traversed after it is cleared.
+       In that case, there is no ring.
+    */
+    if (!here)
+	return 0;
+
+    while (here != &self->ring_home) {
+	cPersistentObject *o = OBJECT_FROM_RING(self, here, "foo");
+	VISIT(o);
+	here = here->r_next;
+    }
+#undef VISIT
+
+    return 0;
+}
+
+static int
+cc_length(ccobject *self)
+{
+    return PyObject_Length(self->data);
+}
+
+static PyObject *
+cc_subscript(ccobject *self, PyObject *key)
+{
+    PyObject *r;
+
+    r = PyDict_GetItem(self->data, key);
+    if (r == NULL) {
+	PyErr_SetObject(PyExc_KeyError, key);
+	return NULL;
+    }
+    Py_INCREF(r);
+
+    return r;
+}
+
+static int
+cc_add_item(ccobject *self, PyObject *key, PyObject *v)
+{
+    int result;
+    PyObject *oid, *object_again, *jar;
+    cPersistentObject *p;
+
+    if (PyType_Check(v)) {
+        /* Its a persistent class, such as a ZClass. Thats ok. */
+    }
+    else if (v->ob_type->tp_basicsize < sizeof(cPersistentObject)) {
+        /* If it's not an instance of a persistent class, (ie Python
+	   classes that derive from persistent.Persistent, BTrees,
+	   etc), report an error.
+
+	   XXX Need a better test.
+	*/
+	PyErr_SetString(PyExc_TypeError,
+			"Cache values must be persistent objects.");
+	return -1;
+    }
+
+    /* Can't access v->oid directly because the object might be a
+     *  persistent class.
+     */
+    oid = PyObject_GetAttr(v, py__p_oid);
+    if (oid == NULL)
+	return -1;
+    if (!PyString_Check(oid)) {
+        PyErr_Format(PyExc_TypeError,
+                     "Cached object oid must be a string, not a %s",
+		     oid->ob_type->tp_name);
+	return -1;
+    }
+    /*  we know they are both strings.
+     *  now check if they are the same string.
+     */
+    result = PyObject_Compare(key, oid);
+    if (PyErr_Occurred()) {
+	Py_DECREF(oid);
+	return -1;
+    }
+    Py_DECREF(oid);
+    if (result) {
+	PyErr_SetString(PyExc_ValueError, "Cache key does not match oid");
+	return -1;
+    }
+
+    /* useful sanity check, but not strictly an invariant of this class */
+    jar = PyObject_GetAttr(v, py__p_jar);
+    if (jar == NULL)
+        return -1;
+    if (jar==Py_None) {
+        Py_DECREF(jar);
+        PyErr_SetString(PyExc_ValueError,
+                        "Cached object jar missing");
+	return -1;
+    }
+    Py_DECREF(jar);
+
+    object_again = PyDict_GetItem(self->data, key);
+    if (object_again) {
+	if (object_again != v) {
+	    PyErr_SetString(PyExc_ValueError,
+		    "Can not re-register object under a different oid");
+	    return -1;
+	} else {
+	    /* re-register under the same oid - no work needed */
+	    return 0;
+	}
+    }
+
+    if (PyType_Check(v)) {
+	if (PyDict_SetItem(self->data, key, v) < 0)
+	    return -1;
+	self->klass_count++;
+	return 0;
+    } else {
+	PerCache *cache = ((cPersistentObject *)v)->cache;
+	if (cache) {
+	    if (cache != (PerCache *)self)
+		/* This object is already in a different cache. */
+		PyErr_SetString(PyExc_ValueError,
+				"Cache values may only be in one cache.");
+	    return -1;
+	}
+	/* else:
+
+	   This object is already one of ours, which is ok.  It
+	   would be very strange if someone was trying to register
+	   the same object under a different key.
+	*/
+    }
+
+    if (PyDict_SetItem(self->data, key, v) < 0)
+	return -1;
+    /* the dict should have a borrowed reference */
+    Py_DECREF(v);
+
+    p = (cPersistentObject *)v;
+    Py_INCREF(self);
+    p->cache = (PerCache *)self;
+    if (p->state >= 0) {
+	/* insert this non-ghost object into the ring just
+	   behind the home position. */
+	self->non_ghost_count++;
+	ring_add(&self->ring_home, &p->ring);
+	/* this list should have a new reference to the object */
+	Py_INCREF(v);
+    }
+    return 0;
+}
+
+static int
+cc_del_item(ccobject *self, PyObject *key)
+{
+    PyObject *v;
+    cPersistentObject *p;
+
+    /* unlink this item from the ring */
+    v = PyDict_GetItem(self->data, key);
+    if (v == NULL)
+	return -1;
+
+    if (PyType_Check(v)) {
+	self->klass_count--;
+    } else {
+	p = (cPersistentObject *)v;
+	if (p->state >= 0) {
+	    self->non_ghost_count--;
+	    ring_del(&p->ring);
+	    /* The DelItem below will account for the reference
+	       held by the list. */
+	} else {
+	    /* This is a ghost object, so we haven't kept a reference
+	       count on it.  For it have stayed alive this long
+	       someone else must be keeping a reference to
+	       it. Therefore we need to temporarily give it back a
+	       reference count before calling DelItem below */
+	    Py_INCREF(v);
+	}
+
+	Py_DECREF((PyObject *)p->cache);
+	p->cache = NULL;
+    }
+
+    if (PyDict_DelItem(self->data, key) < 0) {
+	PyErr_SetString(PyExc_RuntimeError,
+			"unexpectedly couldn't remove key in cc_ass_sub");
+	return -1;
+    }
+
+    return 0;
+}
+
+static int
+cc_ass_sub(ccobject *self, PyObject *key, PyObject *v)
+{
+    if (!PyString_Check(key)) {
+	PyErr_Format(PyExc_TypeError,
+                     "cPickleCache key must be a string, not a %s",
+		     key->ob_type->tp_name);
+	return -1;
+    }
+    if (v)
+	return cc_add_item(self, key, v);
+    else
+	return cc_del_item(self, key);
+}
+
+static PyMappingMethods cc_as_mapping = {
+  (inquiry)cc_length,		/*mp_length*/
+  (binaryfunc)cc_subscript,	/*mp_subscript*/
+  (objobjargproc)cc_ass_sub,	/*mp_ass_subscript*/
+};
+
+static PyObject *
+cc_cache_data(ccobject *self, void *context)
+{
+    return PyDict_Copy(self->data);
+}
+
+static PyGetSetDef cc_getsets[] = {
+    {"cache_data", (getter)cc_cache_data},
+    {NULL}
+};
+
+
+static PyMemberDef cc_members[] = {
+    {"cache_size", T_INT, offsetof(ccobject, cache_size)},
+    {"cache_drain_resistance", T_INT,
+     offsetof(ccobject, cache_drain_resistance)},
+    {"cache_non_ghost_count", T_INT, offsetof(ccobject, non_ghost_count), RO},
+    {"cache_klass_count", T_INT, offsetof(ccobject, klass_count), RO},
+    {NULL}
+};
+
+/* This module is compiled as a shared library.  Some compilers don't
+   allow addresses of Python objects defined in other libraries to be
+   used in static initializers here.  The DEFERRED_ADDRESS macro is
+   used to tag the slots where such addresses appear; the module init
+   function must fill in the tagged slots at runtime.  The argument is
+   for documentation -- the macro ignores it.
+*/
+#define DEFERRED_ADDRESS(ADDR) 0
+
+static PyTypeObject Cctype = {
+    PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type))
+    0,					/* ob_size */
+    "persistent.PickleCache",		/* tp_name */
+    sizeof(ccobject),			/* tp_basicsize */
+    0,					/* tp_itemsize */
+    (destructor)cc_dealloc,		/* tp_dealloc */
+    0,					/* tp_print */
+    0,					/* tp_getattr */
+    0,					/* tp_setattr */
+    0,					/* tp_compare */
+    0,					/* tp_repr */
+    0,					/* tp_as_number */
+    0,					/* tp_as_sequence */
+    &cc_as_mapping,			/* tp_as_mapping */
+    0,					/* tp_hash */
+    0,					/* tp_call */
+    0,					/* tp_str */
+    0,					/* tp_getattro */
+    0,					/* tp_setattro */
+    0,					/* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+    					/* tp_flags */
+    0,					/* tp_doc */
+    (traverseproc)cc_traverse,		/* tp_traverse */
+    (inquiry)cc_clear,			/* tp_clear */
+    0,					/* tp_richcompare */
+    0,					/* tp_weaklistoffset */
+    0,					/* tp_iter */
+    0,					/* tp_iternext */
+    cc_methods,				/* tp_methods */
+    cc_members,				/* tp_members */
+    cc_getsets,				/* tp_getset */
+    0,					/* tp_base */
+    0,					/* tp_dict */
+    0,					/* tp_descr_get */
+    0,					/* tp_descr_set */
+    0,					/* tp_dictoffset */
+    (initproc)cc_init,			/* tp_init */
+};
+
+void
+initcPickleCache(void)
+{
+    PyObject *m;
+
+    Cctype.ob_type = &PyType_Type;
+    Cctype.tp_new = &PyType_GenericNew;
+    if (PyType_Ready(&Cctype) < 0) {
+	return;
+    }
+
+    m = Py_InitModule3("cPickleCache", NULL, cPickleCache_doc_string);
+
+    capi = (cPersistenceCAPIstruct *)PyCObject_Import(
+	"persistent.cPersistence", "CAPI");
+    if (!capi)
+	return;
+    capi->percachedel = (percachedelfunc)cc_oid_unreferenced;
+
+    py_reload = PyString_InternFromString("reload");
+    py__p_jar = PyString_InternFromString("_p_jar");
+    py__p_changed = PyString_InternFromString("_p_changed");
+    py__p_oid = PyString_InternFromString("_p_oid");
+
+    if (PyModule_AddStringConstant(m, "cache_variant", "stiff/c") < 0)
+	return;
+
+    /* This leaks a reference to Cctype, but it doesn't matter. */
+    if (PyModule_AddObject(m, "PickleCache", (PyObject *)&Cctype) < 0)
+	return;
+}


=== Zope/lib/python/persistent/list.py 1.4 => 1.5 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/list.py	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""Python implementation of persistent list.
+
+$Id$"""
+
+__version__='$Revision$'[11:-2]
+
+import persistent
+from UserList import UserList
+
+class PersistentList(UserList, persistent.Persistent):
+    __super_setitem = UserList.__setitem__
+    __super_delitem = UserList.__delitem__
+    __super_setslice = UserList.__setslice__
+    __super_delslice = UserList.__delslice__
+    __super_iadd = UserList.__iadd__
+    __super_imul = UserList.__imul__
+    __super_append = UserList.append
+    __super_insert = UserList.insert
+    __super_pop = UserList.pop
+    __super_remove = UserList.remove
+    __super_reverse = UserList.reverse
+    __super_sort = UserList.sort
+    __super_extend = UserList.extend
+
+    def __setitem__(self, i, item):
+        self.__super_setitem(i, item)
+        self._p_changed = 1
+
+    def __delitem__(self, i):
+        self.__super_delitem(i)
+        self._p_changed = 1
+
+    def __setslice__(self, i, j, other):
+        self.__super_setslice(i, j, other)
+        self._p_changed = 1
+
+    def __delslice__(self, i, j):
+        self.__super_delslice(i, j)
+        self._p_changed = 1
+
+    def __iadd__(self, other):
+        self.__super_iadd(other)
+        self._p_changed = 1
+
+    def __imul__(self, n):
+        self.__super_imul(n)
+        self._p_changed = 1
+
+    def append(self, item):
+        self.__super_append(item)
+        self._p_changed = 1
+
+    def insert(self, i, item):
+        self.__super_insert(i, item)
+        self._p_changed = 1
+
+    def pop(self, i=-1):
+        rtn = self.__super_pop(i)
+        self._p_changed = 1
+        return rtn
+
+    def remove(self, item):
+        self.__super_remove(item)
+        self._p_changed = 1
+
+    def reverse(self):
+        self.__super_reverse()
+        self._p_changed = 1
+
+    def sort(self, *args):
+        self.__super_sort(*args)
+        self._p_changed = 1
+
+    def extend(self, other):
+        self.__super_extend(other)
+        self._p_changed = 1
+
+    # This works around a bug in Python 2.1.x (up to 2.1.2 at least) where the
+    # __cmp__ bogusly raises a RuntimeError, and because this is an extension
+    # class, none of the rich comparison stuff works anyway.
+    def __cmp__(self, other):
+        return cmp(self.data, self._UserList__cast(other))


=== Zope/lib/python/persistent/mapping.py 1.21 => 1.22 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/mapping.py	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,106 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""Python implementation of persistent base types
+
+$Id$"""
+
+__version__='$Revision$'[11:-2]
+
+import persistent
+from UserDict import UserDict
+
+class PersistentMapping(UserDict, persistent.Persistent):
+    """A persistent wrapper for mapping objects.
+
+    This class allows wrapping of mapping objects so that object
+    changes are registered.  As a side effect, mapping objects may be
+    subclassed.
+
+    A subclass of PersistentMapping or any code that adds new
+    attributes should not create an attribute named _container.  This
+    is reserved for backwards compatibility reasons.
+    """
+
+    # UserDict provides all of the mapping behavior.  The
+    # PersistentMapping class is responsible marking the persistent
+    # state as changed when a method actually changes the state.  At
+    # the mapping API evolves, we may need to add more methods here.
+
+    __super_delitem = UserDict.__delitem__
+    __super_setitem = UserDict.__setitem__
+    __super_clear = UserDict.clear
+    __super_update = UserDict.update
+    __super_setdefault = UserDict.setdefault
+
+    def __delitem__(self, key):
+        self.__super_delitem(key)
+        self._p_changed = 1
+
+    def __setitem__(self, key, v):
+        self.__super_setitem(key, v)
+        self._p_changed = 1
+
+    def clear(self):
+        self.__super_clear()
+        self._p_changed = 1
+
+    def update(self, b):
+        self.__super_update(b)
+        self._p_changed = 1
+
+    def setdefault(self, key, failobj=None):
+        # We could inline all of UserDict's implementation into the
+        # method here, but I'd rather not depend at all on the
+        # implementation in UserDict (simple as it is).
+        if not self.has_key(key):
+            self._p_changed = 1
+        return self.__super_setdefault(key, failobj)
+
+    try:
+        __super_popitem = UserDict.popitem
+    except AttributeError:
+        pass
+    else:
+        def popitem(self):
+            self._p_changed = 1
+            return self.__super_popitem()
+
+    # If the internal representation of PersistentMapping changes,
+    # it causes compatibility problems for pickles generated by
+    # different versions of the code.  Compatibility works in both
+    # directions, because an application may want to share a database
+    # between applications using different versions of the code.
+
+    # Effectively, the original rep is part of the "API."  To provide
+    # full compatibility, the getstate and setstate must read and
+    # right objects using the old rep.
+
+    # As a result, the PersistentMapping must save and restore the
+    # actual internal dictionary using the name _container.
+
+    def __getstate__(self):
+        state = {}
+        state.update(self.__dict__)
+        state['_container'] = state['data']
+        del state['data']
+        return state
+
+    def __setstate__(self, state):
+        if state.has_key('_container'):
+            self.data = state['_container']
+            del state['_container']
+        elif not state.has_key('data'):
+            self.data = {}
+        self.__dict__.update(state)


=== Zope/lib/python/persistent/ring.c 1.1 => 1.2 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/ring.c	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,59 @@
+/*****************************************************************************
+
+  Copyright (c) 2003 Zope Corporation and Contributors.
+  All Rights Reserved.
+
+  This software is subject to the provisions of the Zope Public License,
+  Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+  WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+  FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+
+/* Support routines for the doubly-linked list of cached objects.
+
+The cache stores a doubly-linked list of persistent objects, with
+space for the pointers allocated in the objects themselves.  The cache
+stores the distinguished head of the list, which is not a valid
+persistent object.
+
+The next pointers traverse the ring in order starting with the least
+recently used object.  The prev pointers traverse the ring in order
+starting with the most recently used object.
+
+*/
+
+#include "Python.h"
+#include "ring.h"
+
+void
+ring_add(CPersistentRing *ring, CPersistentRing *elt)
+{
+    assert(!elt->r_next);
+    elt->r_next = ring;
+    elt->r_prev = ring->r_prev;
+    ring->r_prev->r_next = elt;
+    ring->r_prev = elt;
+}
+
+void
+ring_del(CPersistentRing *elt)
+{
+    elt->r_next->r_prev = elt->r_prev;
+    elt->r_prev->r_next = elt->r_next;
+    elt->r_next = NULL;
+    elt->r_prev = NULL;
+}
+
+void
+ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt)
+{
+    elt->r_prev->r_next = elt->r_next;
+    elt->r_next->r_prev = elt->r_prev;
+    elt->r_next = ring;
+    elt->r_prev = ring->r_prev;
+    ring->r_prev->r_next = elt;
+    ring->r_prev = elt;
+}


=== Zope/lib/python/persistent/ring.h 1.1 => 1.2 ===
--- /dev/null	Fri Nov 28 11:45:27 2003
+++ Zope/lib/python/persistent/ring.h	Fri Nov 28 11:44:55 2003
@@ -0,0 +1,66 @@
+/*****************************************************************************
+
+  Copyright (c) 2003 Zope Corporation and Contributors.
+  All Rights Reserved.
+
+  This software is subject to the provisions of the Zope Public License,
+  Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+  WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+  FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+
+/* Support routines for the doubly-linked list of cached objects.
+
+The cache stores a headed, doubly-linked, circular list of persistent
+objects, with space for the pointers allocated in the objects themselves.
+The cache stores the distinguished head of the list, which is not a valid
+persistent object.  The other list members are non-ghost persistent
+objects, linked in LRU (least-recently used) order.
+
+The r_next pointers traverse the ring starting with the least recently used
+object.  The r_prev pointers traverse the ring starting with the most
+recently used object.
+
+Obscure:  While each object is pointed at twice by list pointers (once by
+its predecessor's r_next, again by its successor's r_prev), the refcount
+on the object is bumped only by 1.  This leads to some possibly surprising
+sequences of incref and decref code.  Note that since the refcount is
+bumped at least once, the list does hold a strong reference to each
+object in it.
+*/
+
+typedef struct CPersistentRing_struct
+{
+    struct CPersistentRing_struct *r_prev;
+    struct CPersistentRing_struct *r_next;
+} CPersistentRing;
+
+/* The list operations here take constant time independent of the
+ * number of objects in the list:
+ */
+
+/* Add elt as the most recently used object.  elt must not already be
+ * in the list, although this isn't checked.
+ */
+void ring_add(CPersistentRing *ring, CPersistentRing *elt);
+
+/* Remove elt from the list.  elt must already be in the list, although
+ * this isn't checked.
+ */
+void ring_del(CPersistentRing *elt);
+
+/* elt must already be in the list, although this isn't checked.  It's
+ * unlinked from its current position, and relinked into the list as the
+ * most recently used object (which is arguably the tail of the list
+ * instead of the head -- but the name of this function could be argued
+ * either way).  This is equivalent to
+ *
+ *     ring_del(elt);
+ *     ring_add(ring, elt);
+ *
+ * but may be a little quicker.
+ */
+void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt);




More information about the Zodb-checkins mailing list