Check/set connection status at commit inside the critical section

Failing to do so was causing the issue reported in ticket #103. The issue
as reported was fixed when SET ISOLATION LEVEL was dropped, but the real
problem wasn't fixed.
This commit is contained in:
Daniele Varrazzo 2012-02-24 03:04:24 +00:00
parent b2c61eaa18
commit 5fcbe7bd0f
3 changed files with 42 additions and 11 deletions

1
NEWS
View File

@ -12,6 +12,7 @@ What's new in psycopg 2.4.5
- Fixed 'rownumber' during iteration on cursor subclasses. - Fixed 'rownumber' during iteration on cursor subclasses.
Regression introduced in 2.4.4 (ticket #100). Regression introduced in 2.4.4 (ticket #100).
- Added support for 'inet' arrays. - Added support for 'inet' arrays.
- Fixed 'commit()' concurrency problem (ticket #103).
What's new in psycopg 2.4.4 What's new in psycopg 2.4.4

View File

@ -444,34 +444,35 @@ pq_commit(connectionObject *conn)
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&conn->lock);
Dprintf("pq_commit: pgconn = %p, autocommit = %d, status = %d", Dprintf("pq_commit: pgconn = %p, autocommit = %d, status = %d",
conn->pgconn, conn->autocommit, conn->status); conn->pgconn, conn->autocommit, conn->status);
if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) { if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_commit: no transaction to commit"); Dprintf("pq_commit: no transaction to commit");
return 0; retvalue = 0;
} }
else {
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&conn->lock);
conn->mark += 1; conn->mark += 1;
retvalue = pq_execute_command_locked(conn, "COMMIT", &pgres, &error, &_save); retvalue = pq_execute_command_locked(conn, "COMMIT", &pgres, &error, &_save);
}
Py_BLOCK_THREADS; Py_BLOCK_THREADS;
conn_notice_process(conn); conn_notice_process(conn);
Py_UNBLOCK_THREADS; Py_UNBLOCK_THREADS;
/* Even if an error occurred, the connection will be rolled back,
so we unconditionally set the connection status here. */
conn->status = CONN_STATUS_READY;
pthread_mutex_unlock(&conn->lock); pthread_mutex_unlock(&conn->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
if (retvalue < 0) if (retvalue < 0)
pq_complete_error(conn, &pgres, &error); pq_complete_error(conn, &pgres, &error);
/* Even if an error occurred, the connection will be rolled back,
so we unconditionally set the connection status here. */
conn->status = CONN_STATUS_READY;
return retvalue; return retvalue;
} }

View File

@ -166,6 +166,35 @@ class ConnectionTests(unittest.TestCase):
gc.collect() gc.collect()
self.assert_(w() is None) self.assert_(w() is None)
def test_commit_concurrency(self):
# The problem is the one reported in ticket #103. Because of bad
# status check, we commit even when a commit is already on its way.
# We can detect this condition by the warnings.
conn = self.conn
notices = []
stop = []
def committer():
while not stop:
conn.commit()
while conn.notices:
notices.append((2, conn.notices.pop()))
cur = conn.cursor()
t1 = threading.Thread(target=committer)
t1.start()
i = 1
for i in range(1000):
cur.execute("select %s;",(i,))
conn.commit()
while conn.notices:
notices.append((1, conn.notices.pop()))
# Stop the committer thread
stop.append(True)
self.assert_(not notices, "%d notices raised" % len(notices))
class IsolationLevelsTestCase(unittest.TestCase): class IsolationLevelsTestCase(unittest.TestCase):