It is an error to call any Python C APIs while not holding the Python Global Interpreter Lock (GIL). The _curs_doall() function breaks this rule by accessing a list, creating strings and manipulating a dictionary while not holding the GIL. This patch alters _curs_doall() to prepare the information it needs before dropping the GIL. Once the operations have completed and the GIL has been reacquired, it converts any error results to a Python dictionary. -- James Henstridge (2005-03-22) Index: connection.c =================================================================== --- connection.c (revision 693) +++ connection.c (working copy) @@ -50,6 +50,10 @@ } +typedef struct { + cursobject *cursor; + char *errmsg; +} doall_state_t; /* _curs_doall() - execute an operation (commit or rollback) on all the cursors * @@ -65,21 +69,37 @@ int len, i, has_errors = 0; cursobject *cursor; - PyObject* cursors = self->cursors; - PyObject* errs = PyDict_New(); + doall_state_t *cursors = NULL; + PyObject* errs = NULL; Dprintf("curs_doall: acquiring lock\n"); pthread_mutex_lock(&(self->lock)); Dprintf("curs_doall: lock acquired\n"); + + /* collect all the cursors, so we can use them while not holding the GIL + * We keep a reference to the cursors, in case the self->cursors changes + * during the call. */ + len = PyList_Size(self->cursors); + cursors = (doall_state_t *)malloc(len * sizeof (doall_state_t)); + if (!cursors) { + pthread_mutex_unlock(&(self->lock)); + Dprintf("curs_doall: lock released\n"); + return PyErr_NoMemory(); + } + for (i = 0; i < len; i++) { + cursors[i].cursor = (cursobject *)PyList_GetItem(self->cursors, i); + assert(cursors[i].cursor); + Py_INCREF(cursors[i].cursor); + cursors[i].errmsg = NULL; + } + Py_BEGIN_ALLOW_THREADS; - len = PyList_Size(cursors); Dprintf("curs_doall: %d cursors\n", len); /* acquire all the required locks */ for (i = 0; i < len; i++) { - cursor = (cursobject *)PyList_GetItem(cursors, i); - assert(cursor); + cursor = cursors[i].cursor; Dprintf("curs_doall: lock/iterating on %p\n", cursor); if (cursor->keeper->status == KEEPER_BEGIN && cursor->isolation_level > 0){ @@ -98,8 +118,8 @@ /* does all the operations */ for (i = 0; i < len; i++) { int status = 0; - cursor = (cursobject *)PyList_GetItem(cursors, i); - assert(cursor); + + cursor = cursors[i].cursor; Dprintf("curs_doall: iterating on %p\n", cursor); if (cursor->keeper->status == KEEPER_CONN_LOCK) { Dprintf("curs_doall: operating on cursor %p\n", cursor); @@ -107,10 +127,8 @@ status = (*operation)(cursor); if (status == -1) { has_errors = 1; - if (errs) { - PyObject* str = PyString_FromString(cursor->critical); - PyDict_SetItem(errs, (PyObject *)cursor, str); - Py_XDECREF(str); + if (cursor->critical) { + cursors[i].errmsg = strdup(cursor->critical); } } cursor->keeper->status = KEEPER_CONN_READY; @@ -119,8 +137,7 @@ /* unlocks all the connections */ for (i = 0; i < len; i++) { - cursor = (cursobject *)PyList_GetItem(cursors, i); - assert(cursor); + cursor = cursors[i].cursor; if (cursor->keeper->status == KEEPER_CONN_READY) { pthread_mutex_unlock(&(cursor->keeper->lock)); cursor->keeper->status = KEEPER_READY; @@ -133,18 +150,34 @@ Dprintf("curs_doall: lock released\n"); Py_END_ALLOW_THREADS; + /* if an error occurred, set up the error dictionary (or set errs to + * None if we can't create the dictionary). */ if (has_errors) { - if (errs) - return errs; - else { - Py_INCREF(Py_None); - return Py_None; - } + errs = PyDict_New(); + if (errs) { + for (i = 0; i < len; i++) { + if (cursors[i].errmsg != NULL) { + PyObject *str = PyString_FromString(cursors[i].errmsg); + PyDict_SetItem(errs, (PyObject *)cursors[i].cursor, str); + Py_XDECREF(str); + } + } + } else { + errs = Py_None; + Py_INCREF(errs); + } } - else { - Py_DECREF(errs); - return NULL; + + /* clean up the state array */ + for (i = 0; i < len; i++) { + Py_DECREF(cursors[i].cursor); + if (cursors[i].errmsg) + free(cursors[i].errmsg); } + free(cursors); + + /* errs will be NULL if has_errors is False */ + return errs; }