From 47f5e97759879543edd8ee8ad9032ef67ec0567a Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Wed, 5 Apr 2017 14:40:12 +0100 Subject: [PATCH 1/3] Added test to verify #410 The 'unknown error' happens on query. --- tests/test_green.py | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/test_green.py b/tests/test_green.py index 6d1571d4..b8fe4e13 100755 --- a/tests/test_green.py +++ b/tests/test_green.py @@ -112,6 +112,74 @@ class GreenTestCase(ConnectingTestCase): self.assertEqual(curs.fetchone()[0], 1) +class CallbackErrorTestCase(ConnectingTestCase): + def setUp(self): + self._cb = psycopg2.extensions.get_wait_callback() + psycopg2.extensions.set_wait_callback(self.crappy_callback) + ConnectingTestCase.setUp(self) + self.to_error = None + + def tearDown(self): + ConnectingTestCase.tearDown(self) + psycopg2.extensions.set_wait_callback(self._cb) + + def crappy_callback(self, conn): + """green callback failing after `self.to_error` time it is called""" + import select + from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE + + while 1: + if self.to_error is not None: + self.to_error -= 1 + if self.to_error <= 0: + raise ZeroDivisionError("I accidentally the connection") + try: + state = conn.poll() + if state == POLL_OK: + break + elif state == POLL_READ: + select.select([conn.fileno()], [], []) + elif state == POLL_WRITE: + select.select([], [conn.fileno()], []) + else: + raise conn.OperationalError("bad state from poll: %s" % state) + except KeyboardInterrupt: + conn.cancel() + # the loop will be broken by a server error + continue + + def test_errors_on_connection(self): + # Test error propagation in the different stages of the connection + for i in range(100): + self.to_error = i + try: + self.connect() + except ZeroDivisionError: + pass + else: + # We managed to connect + return + + self.fail("you should have had a success or an error by now") + + def test_errors_on_query(self): + for i in range(100): + self.to_error = None + cnn = self.connect() + cur = cnn.cursor() + self.to_error = i + try: + cur.execute("select 1") + cur.fetchone() + except ZeroDivisionError: + pass + else: + # The query completed + return + + self.fail("you should have had a success or an error by now") + + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) From a66c34a6d098623617d675191a4c82f60de1d662 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Wed, 5 Apr 2017 14:05:59 +0100 Subject: [PATCH 2/3] Don't clobber a Python exception with an unknown error Close #410 --- NEWS | 1 + psycopg/pqpath.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 84be2015..a0399a15 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ 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. +- Fixed Python exceptions propagation in green callback (:ticket:`#410`). - Don't display the password in `connection.dsn` when the connection string is specified as an URI (:ticket:`#528`). - Return objects with timezone parsing "infinity" :sql:`timestamptz` diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index f270ce8f..55720724 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -451,6 +451,10 @@ pq_complete_error(connectionObject *conn, PGresult **pgres, char **error) else { if (*error != NULL) { PyErr_SetString(OperationalError, *error); + } else if (PyErr_Occurred()) { + /* There was a Python error (e.g. in the callback). Don't clobber + * it with an unknown exception. (see #410) */ + Dprintf("pq_complete_error: forwarding Python exception"); } else { PyErr_SetString(OperationalError, "unknown error"); } From cd095ef0ee1dac13adb4d9a24260fe610e7a536f Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Wed, 5 Apr 2017 14:43:00 +0100 Subject: [PATCH 3/3] Added test to verify callback errors in named cursors They work fine. --- tests/test_green.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_green.py b/tests/test_green.py index b8fe4e13..8c1c20ce 100755 --- a/tests/test_green.py +++ b/tests/test_green.py @@ -179,6 +179,23 @@ class CallbackErrorTestCase(ConnectingTestCase): self.fail("you should have had a success or an error by now") + def test_errors_named_cursor(self): + for i in range(100): + self.to_error = None + cnn = self.connect() + cur = cnn.cursor('foo') + self.to_error = i + try: + cur.execute("select 1") + cur.fetchone() + except ZeroDivisionError: + pass + else: + # The query completed + return + + self.fail("you should have had a success or an error by now") + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__)