PATCH: cyclic GC traversal for psycopg types

"James Henstridge" <[email protected]>
Newsgroups gmane.comp.python.db.psycopg.devel
Message-ID <[email protected]>
Attached is a patch to add cyclic GC traversal support to the
extension types that Psycopg defines.  I have not implemented the
tp_clear method for any of the types so there can be cases where this
won't result in cycles being broken, but it should at least make them
visible to the GC (and related debugging tools).

If we find cases where objects get involved in unbreakable cycles, we
can look at adding tp_clear implementations (which probably also
involves more extensive checking of the reference handling in said
classes).

James.

_______________________________________________
Psycopg mailing list
Psycopg-IAPFreCvJWPBWskQ1e/[email protected]
http://lists.initd.org/mailman/listinfo/psycopg
psycopg-garbage-collection.patch (text/x-diff, 31.3 KB)
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: [email protected]
# target_branch: bzr+ssh://initd.org/bazaar/psycopg/psycopg2/trunk/
# testament_sha1: 1cade3b30d2e65fa05459031cc13634ead57fb66
# timestamp: 2008-07-21 17:04:07 +0800
# base_revision_id: [email protected]
# 
# Begin patch
=== modified file 'ChangeLog'
--- ChangeLog	2008-05-19 08:14:17 +0000
+++ ChangeLog	2008-07-18 09:42:31 +0000
@@ -1,3 +1,46 @@
+2008-07-18  James Henstridge  <[email protected]>
+
+	* psycopg/adapter_qstring.c (qstring_traverse): add cyclic GC
+	traversal for quoted string adapters.
+
+	* psycopg/adapter_pboolean.c (pboolean_traverse): add cyclic GC
+	traversal for boolean adapters.
+
+	* psycopg/adapter_mxdatetime.c (mxdatetime_traverse): add cyclic
+	GC traversal for mxdatetime adapters.
+
+	* psycopg/adapter_datetime.c (pydatetime_traverse): add cyclic GC
+	traversal for datetime adapters.
+
+2008-07-01  James Henstridge  <[email protected]>
+
+	* psycopg/adapter_binary.c (binary_traverse): add cyclic GC
+	traversal for binary adapters.
+
+	* psycopg/adapter_asis.c (asis_traverse): add cyclic GC traversal
+	for AsIs adapters.
+
+	* psycopg/adapter_list.c (list_traverse): add cyclic GC traversal
+	for list adapters.
+
+2008-05-28  James Henstridge  <[email protected]>
+
+	* psycopg/cursor_type.c (cursor_setup): incref before setting
+	attributes, to make things GC-safe.
+
+	* psycopg/cursor_int.c (curs_reset): make clearing of description
+	and casts attributes GC-safe.
+
+	* psycopg/typecast.c (typecast_traverse): implement cyclic GC
+	traversal for typecasters.
+
+	* psycopg/connection_type.c:
+	* psycopg/cursor_type.c: add support for cyclic GC traversal (no
+	support for clearing).
+
+	* psycopg/python.h: add definitions for Py_CLEAR() and Py_VISIT()
+	for compatibility with old versions of Python.
+
 2008-05-19  Federico Di Gregorio <[email protected]>
 
 	* psycopg/cursor_type.c: fixed memory leak in .executemany(); on

=== modified file 'psycopg/adapter_asis.c'
--- psycopg/adapter_asis.c	2008-01-13 16:05:59 +0000
+++ psycopg/adapter_asis.c	2008-07-21 05:41:54 +0000
@@ -96,8 +96,8 @@
         self, ((PyObject *)self)->ob_refcnt
       );
 
+    Py_INCREF(obj);
     self->wrapped = obj;
-    Py_INCREF(self->wrapped);
 
     Dprintf("asis_setup: good asis object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -106,12 +106,19 @@
     return 0;
 }
 
+static int
+asis_traverse(asisObject *self, visitproc visit, void *arg)
+{
+    Py_VISIT(self->wrapped);
+    return 0;
+}
+
 static void
 asis_dealloc(PyObject* obj)
 {
     asisObject *self = (asisObject *)obj;
 
-    Py_XDECREF(self->wrapped);
+    Py_CLEAR(self->wrapped);
 
     Dprintf("asis_dealloc: deleted asis object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -141,7 +148,7 @@
 static void
 asis_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -183,10 +190,10 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
     asisType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    (traverseproc)asis_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/adapter_binary.c'
--- psycopg/adapter_binary.c	2008-05-05 02:37:24 +0000
+++ psycopg/adapter_binary.c	2008-07-21 05:41:54 +0000
@@ -257,8 +257,8 @@
 
     self->buffer = NULL;
     self->conn = NULL;
+    Py_INCREF(str);
     self->wrapped = str;
-    Py_INCREF(self->wrapped);
 
     Dprintf("binary_setup: good binary object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -266,14 +266,25 @@
     return 0;
 }
 
+static int
+binary_traverse(PyObject *obj, visitproc visit, void *arg)
+{
+    binaryObject *self = (binaryObject *)obj;
+
+    Py_VISIT(self->wrapped);
+    Py_VISIT(self->buffer);
+    Py_VISIT(self->conn);
+    return 0;
+}
+
 static void
 binary_dealloc(PyObject* obj)
 {
     binaryObject *self = (binaryObject *)obj;
 
-    Py_XDECREF(self->wrapped);
-    Py_XDECREF(self->buffer);
-    Py_XDECREF(self->conn);
+    Py_CLEAR(self->wrapped);
+    Py_CLEAR(self->buffer);
+    Py_CLEAR(self->conn);
 
     Dprintf("binary_dealloc: deleted binary object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -303,7 +314,7 @@
 static void
 binary_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -341,11 +352,11 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
 
     binaryType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    binary_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/adapter_datetime.c'
--- psycopg/adapter_datetime.c	2008-05-05 02:37:24 +0000
+++ psycopg/adapter_datetime.c	2008-07-21 05:41:54 +0000
@@ -131,8 +131,8 @@
         self, ((PyObject *)self)->ob_refcnt);
 
     self->type = type;
+    Py_INCREF(obj);
     self->wrapped = obj;
-    Py_INCREF(self->wrapped);
 
     Dprintf("pydatetime_setup: good pydatetime object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -140,12 +140,21 @@
     return 0;
 }
 
+static int
+pydatetime_traverse(PyObject *obj, visitproc visit, void *arg)
+{
+    pydatetimeObject *self = (pydatetimeObject *)obj;
+
+    Py_VISIT(self->wrapped);
+    return 0;
+}
+
 static void
 pydatetime_dealloc(PyObject* obj)
 {
     pydatetimeObject *self = (pydatetimeObject *)obj;
 
-    Py_XDECREF(self->wrapped);
+    Py_CLEAR(self->wrapped);
 
     Dprintf("mpydatetime_dealloc: deleted pydatetime object at %p, "
             "refcnt = " FORMAT_CODE_PY_SSIZE_T, obj, obj->ob_refcnt);
@@ -174,7 +183,7 @@
 static void
 pydatetime_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -213,11 +222,11 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
 
     pydatetimeType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    pydatetime_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/adapter_list.c'
--- psycopg/adapter_list.c	2008-05-05 02:37:24 +0000
+++ psycopg/adapter_list.c	2008-07-21 05:41:54 +0000
@@ -108,9 +108,9 @@
        reference to it; we'll need it during the recursive adapt() call (the
        encoding is here for a future expansion that will make .getquoted()
        work even without a connection to the backend. */
-    Py_XDECREF(self->connection);
+    Py_CLEAR(self->connection);
+    Py_INCREF(conn);
     self->connection = (PyObject*)conn;
-    Py_INCREF(self->connection);
 
     Py_INCREF(Py_None);
     return Py_None;
@@ -169,8 +169,8 @@
     if (enc) self->encoding = strdup(enc);
 
     self->connection = NULL;
+    Py_INCREF(obj);
     self->wrapped = obj;
-    Py_INCREF(self->wrapped);
 
     Dprintf("list_setup: good list object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -179,13 +179,23 @@
     return 0;
 }
 
+static int
+list_traverse(PyObject *obj, visitproc visit, void *arg)
+{
+    listObject *self = (listObject *)obj;
+
+    Py_VISIT(self->wrapped);
+    Py_VISIT(self->connection);
+    return 0;
+}
+
 static void
 list_dealloc(PyObject* obj)
 {
     listObject *self = (listObject *)obj;
 
-    Py_XDECREF(self->wrapped);
-    Py_XDECREF(self->connection);
+    Py_CLEAR(self->wrapped);
+    Py_CLEAR(self->connection);
     if (self->encoding) free(self->encoding);
 
     Dprintf("list_dealloc: deleted list object at %p, "
@@ -215,7 +225,7 @@
 static void
 list_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -253,11 +263,11 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
 
     listType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    list_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/adapter_mxdatetime.c'
--- psycopg/adapter_mxdatetime.c	2008-05-05 02:37:24 +0000
+++ psycopg/adapter_mxdatetime.c	2008-07-21 05:41:54 +0000
@@ -152,8 +152,8 @@
       );
 
     self->type = type;
+    Py_INCREF(obj);
     self->wrapped = obj;
-    Py_INCREF(self->wrapped);
 
     Dprintf("mxdatetime_setup: good mxdatetime object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -162,12 +162,21 @@
     return 0;
 }
 
+static int
+mxdatetime_traverse(PyObject *obj, visitproc visit, void *arg)
+{
+    mxdatetimeObject *self = (mxdatetimeObject *)obj;
+
+    Py_VISIT(self->wrapped);
+    return 0;
+}
+
 static void
 mxdatetime_dealloc(PyObject* obj)
 {
     mxdatetimeObject *self = (mxdatetimeObject *)obj;
 
-    Py_XDECREF(self->wrapped);
+    Py_CLEAR(self->wrapped);
 
     Dprintf("mxdatetime_dealloc: deleted mxdatetime object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -198,7 +207,7 @@
 static void
 mxdatetime_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -237,11 +246,11 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
 
     mxdatetimeType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    mxdatetime_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/adapter_pboolean.c'
--- psycopg/adapter_pboolean.c	2008-01-13 16:05:59 +0000
+++ psycopg/adapter_pboolean.c	2008-07-21 05:41:54 +0000
@@ -106,8 +106,8 @@
         self, ((PyObject *)self)->ob_refcnt
       );
 
+    Py_INCREF(obj);
     self->wrapped = obj;
-    Py_INCREF(self->wrapped);
 
     Dprintf("pboolean_setup: good pboolean object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -116,12 +116,21 @@
     return 0;
 }
 
+static int
+pboolean_traverse(PyObject *obj, visitproc visit, void *arg)
+{
+    pbooleanObject *self = (pbooleanObject *)obj;
+
+    Py_VISIT(self->wrapped);
+    return 0;
+}
+
 static void
 pboolean_dealloc(PyObject* obj)
 {
     pbooleanObject *self = (pbooleanObject *)obj;
 
-    Py_XDECREF(self->wrapped);
+    Py_CLEAR(self->wrapped);
 
     Dprintf("pboolean_dealloc: deleted pboolean object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -151,7 +160,7 @@
 static void
 pboolean_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -194,10 +203,10 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
     pbooleanType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    pboolean_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/adapter_qstring.c'
--- psycopg/adapter_qstring.c	2008-05-05 02:37:24 +0000
+++ psycopg/adapter_qstring.c	2008-07-21 05:41:54 +0000
@@ -216,10 +216,10 @@
         Dprintf("qstring_prepare: set encoding to %s", conn->encoding);
     }
 
-    Py_XDECREF(self->conn);
+    Py_CLEAR(self->conn);
     if (conn) {
+        Py_INCREF(conn);
         self->conn = (PyObject*)conn;
-        Py_INCREF(self->conn);
     }
 
     Py_INCREF(Py_None);
@@ -280,8 +280,8 @@
     /* FIXME: remove this orrible strdup */
     if (enc) self->encoding = strdup(enc);
 
+    Py_INCREF(str);
     self->wrapped = str;
-    Py_INCREF(self->wrapped);
 
     Dprintf("qstring_setup: good qstring object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -290,14 +290,25 @@
     return 0;
 }
 
+static int
+qstring_traverse(PyObject *obj, visitproc visit, void *arg)
+{
+    qstringObject *self = (qstringObject *)obj;
+
+    Py_VISIT(self->wrapped);
+    Py_VISIT(self->buffer);
+    Py_VISIT(self->conn);
+    return 0;
+}
+
 static void
 qstring_dealloc(PyObject* obj)
 {
     qstringObject *self = (qstringObject *)obj;
 
-    Py_XDECREF(self->wrapped);
-    Py_XDECREF(self->buffer);
-    Py_XDECREF(self->conn);
+    Py_CLEAR(self->wrapped);
+    Py_CLEAR(self->buffer);
+    Py_CLEAR(self->conn);
 
     if (self->encoding) free(self->encoding);
 
@@ -330,7 +341,7 @@
 static void
 qstring_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -369,11 +380,11 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
 
     qstringType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    qstring_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/connection_type.c'
--- psycopg/connection_type.c	2008-05-05 02:43:38 +0000
+++ psycopg/connection_type.c	2008-05-28 01:00:36 +0000
@@ -450,11 +450,11 @@
     if (self->encoding) free(self->encoding);
     if (self->critical) free(self->critical);
 
-    Py_XDECREF(self->notice_list);
-    Py_XDECREF(self->notifies);
-    Py_XDECREF(self->async_cursor);
-    Py_XDECREF(self->string_types);
-    Py_XDECREF(self->binary_types);
+    Py_CLEAR(self->notice_list);
+    Py_CLEAR(self->notifies);
+    Py_CLEAR(self->async_cursor);
+    Py_CLEAR(self->string_types);
+    Py_CLEAR(self->binary_types);
 
     pthread_mutex_destroy(&(self->lock));
 
@@ -486,7 +486,7 @@
 static void
 connection_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -497,6 +497,18 @@
         self, self->dsn, self->closed);
 }
 
+static int
+connection_traverse(connectionObject *self, visitproc visit, void *arg)
+{
+    Py_VISIT(self->async_cursor);
+    Py_VISIT(self->notice_list);
+    Py_VISIT(self->notice_filter);
+    Py_VISIT(self->notifies);
+    Py_VISIT(self->string_types);
+    Py_VISIT(self->binary_types);
+    return 0;
+}
+
 
 /* object type */
 
@@ -530,10 +542,10 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
     connectionType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    (traverseproc)connection_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/cursor_int.c'
--- psycopg/cursor_int.c	2007-04-10 06:36:18 +0000
+++ psycopg/cursor_int.c	2008-05-28 09:45:37 +0000
@@ -34,15 +34,19 @@
 void
 curs_reset(cursorObject *self)
 {
+    PyObject *tmp;
+
     /* initialize some variables to default values */
     self->notuples = 1;
     self->rowcount = -1;
     self->row = 0;
 
-    Py_XDECREF(self->description);
+    tmp = self->description;
     Py_INCREF(Py_None);
     self->description = Py_None;
+    Py_XDECREF(tmp);
 
-    Py_XDECREF(self->casts);
+    tmp = self->casts;
     self->casts = NULL;
+    Py_XDECREF(tmp);
 }

=== modified file 'psycopg/cursor_type.c'
--- psycopg/cursor_type.c	2008-05-19 08:14:17 +0000
+++ psycopg/cursor_type.c	2008-07-21 05:41:54 +0000
@@ -1590,8 +1590,8 @@
             "argument 1 must be subclass of psycopg2._psycopg.connection");
         return 1;
     } */
+    Py_INCREF(conn);
     self->conn = conn;
-    Py_INCREF((PyObject*)self->conn);
 
     self->closed = 0;
     self->mark = conn->mark;
@@ -1607,6 +1607,7 @@
     self->string_types = NULL;
     self->binary_types = NULL;
 
+    Py_INCREF(Py_None);
     self->description = Py_None;
     Py_INCREF(Py_None);
     self->pgstatus = Py_None;
@@ -1614,11 +1615,10 @@
     self->tuple_factory = Py_None;
     Py_INCREF(Py_None);
     self->query = Py_None;
-    Py_INCREF(Py_None);
 
     /* default tzinfo factory */
+    Py_INCREF(pyPsycopgTzFixedOffsetTimezone);
     self->tzinfo_factory = pyPsycopgTzFixedOffsetTimezone;
-    Py_INCREF(self->tzinfo_factory);
 
     Dprintf("cursor_setup: good cursor object at %p, refcnt = "
         FORMAT_CODE_PY_SSIZE_T,
@@ -1634,15 +1634,15 @@
 
     if (self->name) PyMem_Free(self->name);
 
-    Py_XDECREF((PyObject*)self->conn);
-    Py_XDECREF(self->casts);
-    Py_XDECREF(self->description);
-    Py_XDECREF(self->pgstatus);
-    Py_XDECREF(self->tuple_factory);
-    Py_XDECREF(self->tzinfo_factory);
-    Py_XDECREF(self->query);
-    Py_XDECREF(self->string_types);
-    Py_XDECREF(self->binary_types);
+    Py_CLEAR(self->conn);
+    Py_CLEAR(self->casts);
+    Py_CLEAR(self->description);
+    Py_CLEAR(self->pgstatus);
+    Py_CLEAR(self->tuple_factory);
+    Py_CLEAR(self->tzinfo_factory);
+    Py_CLEAR(self->query);
+    Py_CLEAR(self->string_types);
+    Py_CLEAR(self->binary_types);
 
     IFCLEARPGRES(self->pgres);
 
@@ -1675,7 +1675,7 @@
 static void
 cursor_del(PyObject* self)
 {
-    PyObject_Del(self);
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -1685,6 +1685,23 @@
         "<cursor object at %p; closed: %d>", self, self->closed);
 }
 
+static int
+cursor_traverse(cursorObject *self, visitproc visit, void *arg)
+{
+    Py_VISIT((PyObject *)self->conn);
+    Py_VISIT(self->description);
+    Py_VISIT(self->pgstatus);
+    Py_VISIT(self->casts);
+    Py_VISIT(self->caster);
+    Py_VISIT(self->copyfile);
+    Py_VISIT(self->tuple_factory);
+    Py_VISIT(self->tzinfo_factory);
+    Py_VISIT(self->query);
+    Py_VISIT(self->string_types);
+    Py_VISIT(self->binary_types);
+    return 0;
+}
+
 
 /* object type */
 
@@ -1714,10 +1731,11 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER |
+      Py_TPFLAGS_HAVE_GC, /*tp_flags*/
     cursorType_doc, /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    (traverseproc)cursor_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     0,          /*tp_richcompare*/

=== modified file 'psycopg/python.h'
--- psycopg/python.h	2007-04-10 06:36:18 +0000
+++ psycopg/python.h	2008-05-28 01:00:36 +0000
@@ -41,4 +41,27 @@
 #define freefunc destructor
 #endif
 
+/* Py_VISIT and Py_CLEAR introduced in Python 2.4 */
+#ifndef Py_VISIT
+#define Py_VISIT(op)                                    \
+        do {                                            \
+                if (op) {                               \
+                        int vret = visit((op), arg);    \
+                        if (vret)                       \
+                                return vret;            \
+                }                                       \
+        } while (0)
+#endif
+
+#ifndef Py_CLEAR
+#define Py_CLEAR(op)                            \
+        do {                                    \
+                if (op) {                       \
+                        PyObject *tmp = (PyObject *)(op);       \
+                        (op) = NULL;            \
+                        Py_DECREF(tmp);         \
+                }                               \
+        } while (0)
+#endif
+
 #endif /* !defined(PSYCOPG_PYTHON_H) */

=== modified file 'psycopg/typecast.c'
--- psycopg/typecast.c	2008-05-05 02:37:24 +0000
+++ psycopg/typecast.c	2008-05-28 09:22:40 +0000
@@ -382,11 +382,28 @@
 {
     typecastObject *self = (typecastObject*)obj;
 
-    Py_XDECREF(self->values);
-    Py_XDECREF(self->name);
-    Py_XDECREF(self->pcast);
-
-    PyObject_Del(self);
+    Py_CLEAR(self->values);
+    Py_CLEAR(self->name);
+    Py_CLEAR(self->pcast);
+
+    obj->ob_type->tp_free(obj);
+}
+
+static int
+typecast_traverse(PyObject *obj, visitproc visit, void *arg)
+{
+    typecastObject *self = (typecastObject*)obj;
+
+    Py_VISIT(self->values);
+    Py_VISIT(self->name);
+    Py_VISIT(self->pcast);
+    return 0;
+}
+
+static void
+typecast_del(void *self)
+{
+    PyObject_GC_Del(self);
 }
 
 static PyObject *
@@ -427,10 +444,11 @@
     0,          /*tp_setattro*/
     0,          /*tp_as_buffer*/
 
-    Py_TPFLAGS_HAVE_RICHCOMPARE, /*tp_flags*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_RICHCOMPARE |
+      Py_TPFLAGS_HAVE_GC, /*tp_flags*/
     "psycopg type-casting object", /*tp_doc*/
 
-    0,          /*tp_traverse*/
+    typecast_traverse, /*tp_traverse*/
     0,          /*tp_clear*/
 
     typecast_richcompare, /*tp_richcompare*/
@@ -454,7 +472,7 @@
     0, /*tp_init*/
     0, /*tp_alloc  will be set to PyType_GenericAlloc in module init*/
     0, /*tp_new*/
-    0, /*tp_free  Low-level free-memory routine */
+    typecast_del, /*tp_free  Low-level free-memory routine */
     0,          /*tp_is_gc For PyObject_IS_GC */
     0,          /*tp_bases*/
     0,          /*tp_mro method resolution order */
@@ -468,7 +486,7 @@
 {
     typecastObject *obj;
 
-    obj = PyObject_NEW(typecastObject, &typecastType);
+    obj = PyObject_GC_New(typecastObject, &typecastType);
     if (obj == NULL) return NULL;
 
     Dprintf("typecast_new: new type at = %p, refcnt = " FORMAT_CODE_PY_SSIZE_T,
@@ -498,6 +516,8 @@
         obj->pcast = cast;
     }
 
+    PyObject_GC_Track(obj);
+
     Dprintf("typecast_new: typecast object created at %p", obj);
 
     return (PyObject *)obj;

# Begin bundle
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.