[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