Merge branch 'fix-443'

This commit is contained in:
Daniele Varrazzo 2017-03-14 14:41:48 +00:00
commit 7187d6408a
4 changed files with 89 additions and 27 deletions

13
NEWS
View File

@ -1,6 +1,14 @@
Current release
---------------
What's new in psycopg 2.7.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed inconsistent state in externally closed connections
(:tickets:`#263, #311, #443`). Was fixed in 2.6.2 but not included in
2.7 by mistake.
What's new in psycopg 2.7.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -82,6 +90,8 @@ What's new in psycopg 2.6.3
What's new in psycopg 2.6.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed inconsistent state in externally closed connections
(:tickets:`#263, #311, #443`).
- Report the server response status on errors (such as :ticket:`#281`).
- Raise `!NotSupportedError` on unhandled server response status
(:ticket:`#352`).
@ -91,7 +101,8 @@ What's new in psycopg 2.6.2
(:ticket:`#333`).
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
- Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`).
- Allow adapting bytes using QuotedString on Python 3 too (:ticket:`#365`).
- Allow adapting bytes using `~psycopg2.extensions.QuotedString` on Python 3
(:ticket:`#365`).
- Added support for setuptools/wheel (:ticket:`#370`).
- Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`).
- Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`).

View File

@ -179,8 +179,10 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
/* if the connection has somehow been broken, we mark the connection
object as closed but requiring cleanup */
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD)
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD) {
conn->closed = 2;
exc = OperationalError;
}
if (pgres == NULL && curs != NULL)
pgres = &curs->pgres;
@ -214,9 +216,9 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
if (code != NULL) {
exc = exception_from_sqlstate(code);
}
else {
/* Fallback if there is no exception code (reported happening e.g.
* when the connection is closed). */
else if (exc == NULL) {
/* Fallback if there is no exception code (unless we already
determined that the connection was closed). */
exc = DatabaseError;
}
@ -986,6 +988,9 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
/* don't let pgres = NULL go to pq_fetch() */
if (curs->pgres == NULL) {
if (CONNECTION_BAD == PQstatus(curs->conn->pgconn)) {
curs->conn->closed = 2;
}
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
if (!PyErr_Occurred()) {
@ -1013,6 +1018,9 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
CLEARPGRES(curs->pgres);
if (PQsendQuery(curs->conn->pgconn, query) == 0) {
if (CONNECTION_BAD == PQstatus(curs->conn->pgconn)) {
curs->conn->closed = 2;
}
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
PyErr_SetString(OperationalError,

View File

@ -72,16 +72,8 @@ class ConnectionTests(ConnectingTestCase):
# ticket #148
conn = self.conn
cur = conn.cursor()
try:
cur.execute("select pg_terminate_backend(pg_backend_pid())")
except psycopg2.OperationalError, e:
if e.pgcode != psycopg2.errorcodes.ADMIN_SHUTDOWN:
raise
except psycopg2.DatabaseError, e:
# curiously when disconnected in green mode we get a DatabaseError
# without pgcode.
if e.pgcode is not None:
raise
self.assertRaises(psycopg2.OperationalError,
cur.execute, "select pg_terminate_backend(pg_backend_pid())")
self.assertEqual(conn.closed, 2)
conn.close()

View File

@ -27,7 +27,10 @@ import pickle
import psycopg2
import psycopg2.extensions
from testutils import (unittest, ConnectingTestCase, skip_before_postgres,
skip_if_no_namedtuple, skip_if_no_getrefcount, slow)
skip_if_no_namedtuple, skip_if_no_getrefcount, slow, skip_if_no_superuser,
skip_if_windows)
import psycopg2.extras
class CursorTests(ConnectingTestCase):
@ -518,7 +521,7 @@ class CursorTests(ConnectingTestCase):
RETURNS INT AS
'SELECT $1 * $1'
LANGUAGE SQL
''' % (procname, escaped_paramname));
''' % (procname, escaped_paramname))
# Make sure callproc works right
cur.callproc(procname, {paramname: 2})
@ -537,9 +540,57 @@ class CursorTests(ConnectingTestCase):
self.assertRaises(exception, cur.callproc, procname, parameter_sequence)
self.conn.rollback()
def test_callproc_badparam(self):
cur = self.conn.cursor()
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
@skip_if_no_superuser
@skip_if_windows
@skip_before_postgres(8, 4)
def test_external_close_sync(self):
# If a "victim" connection is closed by a "control" connection
# behind psycopg2's back, psycopg2 always handles it correctly:
# raise OperationalError, set conn.closed to 2. This reproduces
# issue #443, a race between control_conn closing victim_conn and
# psycopg2 noticing.
control_conn = self.conn
connect_func = self.connect
wait_func = lambda conn: None
self._test_external_close(control_conn, connect_func, wait_func)
@skip_if_no_superuser
@skip_if_windows
@skip_before_postgres(8, 4)
def test_external_close_async(self):
# Issue #443 is in the async code too. Since the fix is duplicated,
# so is the test.
control_conn = self.conn
connect_func = lambda: self.connect(async=True)
wait_func = psycopg2.extras.wait_select
self._test_external_close(control_conn, connect_func, wait_func)
def _test_external_close(self, control_conn, connect_func, wait_func):
# The short sleep before using victim_conn the second time makes it
# much more likely to lose the race and see the bug. Repeating the
# test several times makes it even more likely.
for i in range(10):
victim_conn = connect_func()
wait_func(victim_conn)
with victim_conn.cursor() as cur:
cur.execute('select pg_backend_pid()')
wait_func(victim_conn)
pid1 = cur.fetchall()[0][0]
with control_conn.cursor() as cur:
cur.execute('select pg_terminate_backend(%s)', (pid1,))
time.sleep(0.001)
def f():
with victim_conn.cursor() as cur:
cur.execute('select 1')
wait_func(victim_conn)
self.assertRaises(psycopg2.OperationalError, f)
self.assertEqual(victim_conn.closed, 2)
def test_suite():