[PATCH] Fix refcounting bug in typecaster

Michael Tharp <gxti-1pawZKhx9Om5WRpDikjj11aTQe2KTcn/@public.gmane.org> Wed, 07 Apr 2010 15:19:46 -0400
Newsgroups gmane.comp.python.db.psycopg.devel
Message-ID <[email protected]>
Greetings!

I have found the cause of the assertion error from earlier. The key was 
getting it through my insufficiently-caffeinated skull that the crash 
was happening in the middle of a call to the caster function, where a 
new object being instantiated triggered a garbage collection. The GC 
then trips over a too-small refcount on the type object which is 
reachable twice even though the refcnt is only 1.

Here's a test case, I don't know if you want to include it in the test 
suite so I didn't write one.
---SNIP---
import gc
import psycopg2
from psycopg2 import extensions

db = psycopg2.connect('')

def caster(val, cur):
     gc.collect()
     return 42

typ = extensions.new_type((2950,), "UUID", caster)
extensions.register_type(typ, db)
cu = db.cursor()
cu.execute("select 'b5219e01-19ab-4994-b71e-149225dc51e4'::uuid")
cu.fetchone()
---SNIP---

Moral of the story: Don't Py_VISIT objects via a stolen reference.

Patch is attached. Keep up the great work!

-- m. tharp

_______________________________________________
Psycopg mailing list
Psycopg-IAPFreCvJWPBWskQ1e/[email protected]
http://lists.initd.org/mailman/listinfo/psycopg
0001-Fix-refcounting-bug-in-typecaster.patch (text/plain, 1.7 KB)
From 5e5d63544675df91a53cfbee7d9d5c287b10d23b Mon Sep 17 00:00:00 2001
From: Michael Tharp <gxti-1pawZKhx9Om5WRpDikjj11aTQe2KTcn/@public.gmane.org>
Date: Wed, 7 Apr 2010 15:01:57 -0400
Subject: [PATCH] Fix refcounting bug in typecaster

This could cause an assertion if a garbage collection happened in the
middle of a caster function, which is quite probable since caster
functions are usually allocating new instances.
---
 psycopg/typecast.c |    7 ++++++-
 1 files changed, 6 insertions(+), 1 deletions(-)

diff --git a/psycopg/typecast.c b/psycopg/typecast.c
index 363196c..326d3ea 100644
--- a/psycopg/typecast.c
+++ b/psycopg/typecast.c
@@ -381,9 +381,12 @@ typecast_dealloc(PyObject *obj)
 {
     typecastObject *self = (typecastObject*)obj;
 
+    PyObject_GC_UnTrack(self);
+
     Py_CLEAR(self->values);
     Py_CLEAR(self->name);
     Py_CLEAR(self->pcast);
+    Py_CLEAR(self->bcast);
 
     obj->ob_type->tp_free(obj);
 }
@@ -396,6 +399,7 @@ typecast_traverse(PyObject *obj, visitproc visit, void *arg)
     Py_VISIT(self->values);
     Py_VISIT(self->name);
     Py_VISIT(self->pcast);
+    Py_VISIT(self->bcast);
     return 0;
 }
 
@@ -587,7 +591,7 @@ typecast_cast(PyObject *obj, const char *str, Py_ssize_t len, PyObject *curs)
     PyObject *old, *res = NULL;
     typecastObject *self = (typecastObject *)obj;
 
-    /* we don't incref, the caster *can't* die at this point */
+    Py_INCREF(obj);
     old = ((cursorObject*)curs)->caster;
     ((cursorObject*)curs)->caster = obj;
 
@@ -602,6 +606,7 @@ typecast_cast(PyObject *obj, const char *str, Py_ssize_t len, PyObject *curs)
     }
 
     ((cursorObject*)curs)->caster = old;
+    Py_DECREF(obj);
 
     return res;
 }
-- 
1.6.6.1