Re: psycopg2 2.0.8 - segmentation fault

"Gangadharan S.A." <[email protected]>
Newsgroups gmane.comp.python.db.psycopg.devel
Message-ID <[email protected]>
>
> Program received signal SIGABRT, Aborted.
> [Switching to Thread 1283557696 (LWP 12437)]
> 0x00002b2fdc478535 in raise () from /lib64/libc.so.6
> (gdb) bt
> #0  0x00002b2fdc478535 in raise () from /lib64/libc.so.6
> #1  0x00002b2fdc479990 in abort () from /lib64/libc.so.6
> #2  0x00002b2fdc4af6db in __libc_message () from /lib64/libc.so.6
> #3  0x00002b2fdc4b48fe in malloc_printerr () from /lib64/libc.so.6
> #4  0x00002b2fdc4b5f36 in free () from /lib64/libc.so.6
> #5  0x00002b2fe062f7e7 in connection_dealloc (obj=0x2aaaaac0ef30) at
> psycopg/connection_type.c:464


I managed to reproduce and fix the issue. This is happening because when
dealloc-ing, the "Py_BEGIN_ALLOW_THREADS;" in conn_close is letting other
threads launch the garbage collector which in turn ends up running the
dealloc a second time.

The fix is to untrack the connnection object from GC, before going ahead
with dealloc, as said in http://docs.python.org/c-api/gcsupport.html

"Similarly, the deallocator for the object must conform to a similar pair of
rules:

   1. Before fields which refer to other containers are invalidated,
   PyObject_GC_UnTrack<http://docs.python.org/c-api/gcsupport.html#PyObject_GC_UnTrack>must
be called.
   2. The object’s memory must be deallocated using
PyObject_GC_Del<http://docs.python.org/c-api/gcsupport.html#PyObject_GC_Del>
   ."

I have attached script to reproduce the issue and the 1 line patch against
current trunk to fix it.

Thanks,
Gangadharan

_______________________________________________
Psycopg mailing list
Psycopg-IAPFreCvJWPBWskQ1e/[email protected]
http://lists.initd.org/mailman/listinfo/psycopg
double_dealloc_fix.diff (text/x-patch, 355 B)
=== modified file 'psycopg/connection_type.c'
--- psycopg/connection_type.c	2009-04-04 17:17:40 +0000
+++ psycopg/connection_type.c	2009-04-15 16:51:49 +0000
@@ -461,6 +461,8 @@
 {
     connectionObject *self = (connectionObject *)obj;
 
+    PyObject_GC_UnTrack(self);
+
     if (self->closed == 0) conn_close(self);
     
     conn_notice_clean(self);
trigger_double_dealloc.py (text/x-python, 1.9 KB)
import psycopg2, psycopg2.extensions
import threading
import gc
import time
import sys

# inherit psycopg2 connection class just so that
# garbage collector enters the tp_clear code path
# in delete_garbage()

class my_connection(psycopg2.extensions.connection):
    pass

class db_user(threading.Thread):
    def run(self):
        conn2 = psycopg2.connect(sys.argv[1], connection_factory=my_connection)
        cursor = conn2.cursor()
        cursor.execute("UPDATE test_psycopg2_dealloc SET a = 3", async=1)

        # the conn2 desctructor will block indefinitely
        # on the completion of the query
        # (and it will not be holding the GIL during that time)
        print >> sys.stderr, "begin conn2 del"
        del cursor, conn2
        print >> sys.stderr, "end conn2 del"

def main():
    # lock out a db row
    conn1 = psycopg2.connect(sys.argv[1], connection_factory=my_connection)
    cursor = conn1.cursor()
    cursor.execute("DROP TABLE IF EXISTS test_psycopg2_dealloc")
    cursor.execute("CREATE TABLE test_psycopg2_dealloc (a int)")
    cursor.execute("INSERT INTO test_psycopg2_dealloc VALUES (1)")
    conn1.commit()
    cursor.execute("UPDATE test_psycopg2_dealloc SET a = 2", async=1)

    # concurrent thread trying to access the locked row
    db_user().start()

    # eventually, a gc.collect run will happen
    # while the conn2 is inside conn_close()
    # but this second dealloc won't get blocked
    # as it will avoid conn_close()
    for i in range(10):
        if gc.collect():
            print >> sys.stderr, "garbage collection done"
            break
        time.sleep(1)

    # we now unlock the row by invoking
    # the desctructor of conn1. This will permit the
    # concurrent thread destructor of conn2 to
    # continue and it will end up trying to free
    # self->dsn a second time.
    print >> sys.stderr, "begin conn1 del"
    del cursor, conn1
    print >> sys.stderr, "end conn1 del"


if __name__ == '__main__':
    main()
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.