From: Daniele Varrazzo Date: Wed, 7 May 2025 21:39:20 +0000 (+0200) Subject: fix: don't process further attempt if ctrl-c is pressed during connection X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F1077%2Fhead;p=thirdparty%2Fpsycopg.git fix: don't process further attempt if ctrl-c is pressed during connection See for example #1055 where this behaviour is reported, #1076 where it is further discussed. --- diff --git a/docs/news.rst b/docs/news.rst index b2446fa1c..e40383436 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -29,6 +29,7 @@ Psycopg 3.2.8 (unreleased) `AsyncServerCursor` (:ticket:`#1066`). - Fix interval parsing with days or other parts and negative time in C module (:ticket:`#1071`). +- Don't process further connection attempts after Ctrl-C (:ticket:`#1077`). Psycopg 3.2.7 diff --git a/psycopg/psycopg/connection.py b/psycopg/psycopg/connection.py index 156ff82ff..8ee5128ed 100644 --- a/psycopg/psycopg/connection.py +++ b/psycopg/psycopg/connection.py @@ -100,7 +100,7 @@ class Connection(BaseConnection[Row]): conninfo = make_conninfo("", **attempt) gen = cls._connect_gen(conninfo, timeout=timeout) rv = waiting.wait_conn(gen, interval=_WAIT_INTERVAL) - except e._NO_TRACEBACK as ex: + except e.Error as ex: if len(attempts) > 1: logger.debug( "connection attempt failed: host: %r port: %r, hostaddr %r: %s", @@ -110,6 +110,8 @@ class Connection(BaseConnection[Row]): str(ex), ) last_ex = ex + except e._NO_TRACEBACK as ex: + raise ex.with_traceback(None) else: break diff --git a/psycopg/psycopg/connection_async.py b/psycopg/psycopg/connection_async.py index a2397ed2f..ddfccae1a 100644 --- a/psycopg/psycopg/connection_async.py +++ b/psycopg/psycopg/connection_async.py @@ -116,7 +116,7 @@ class AsyncConnection(BaseConnection[Row]): conninfo = make_conninfo("", **attempt) gen = cls._connect_gen(conninfo, timeout=timeout) rv = await waiting.wait_conn_async(gen, interval=_WAIT_INTERVAL) - except e._NO_TRACEBACK as ex: + except e.Error as ex: if len(attempts) > 1: logger.debug( "connection attempt failed: host: %r port: %r, hostaddr %r: %s", @@ -126,6 +126,8 @@ class AsyncConnection(BaseConnection[Row]): str(ex), ) last_ex = ex + except e._NO_TRACEBACK as ex: + raise ex.with_traceback(None) else: break diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 488b197c0..4b5b760c8 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -15,6 +15,7 @@ import pytest import psycopg from psycopg import errors as e +from psycopg.conninfo import conninfo_to_dict, make_conninfo @pytest.mark.slow @@ -515,3 +516,52 @@ def test_transaction_concurrency(conn, what): t1.join() evs[2].set() t2.join() + + +@pytest.mark.slow +@pytest.mark.subprocess +@pytest.mark.skipif( + sys.platform == "win32", reason="don't know how to Ctrl-C on Windows" +) +@pytest.mark.crdb("skip") +def test_break_attempts(dsn, proxy): + with proxy.deaf_listen(): + # Prepare a connection string that will fail the first attempt + # but will succeed the second. + dsn1 = conninfo_to_dict(proxy.client_dsn) + dsn2 = conninfo_to_dict(dsn) + dsn = dsn1.copy() + for k in "host port hostaddr".split(): + if k in dsn1 or k in dsn2: + dsn[k] = f"{dsn1.get(k) or ''},{dsn2.get(k) or ''}" + dsn["connect_timeout"] = 3 + dsn = make_conninfo(**dsn) + + # Run a script to try to connect with this connection string + script = f"import psycopg; print(psycopg.connect({dsn!r}))" + proc = None + stdout = stderr = "" + + def run_process(): + nonlocal proc, stdout, stderr + cmdline = [sys.executable, "-s", "-c", script] + proc = sp.Popen(cmdline, text=True, stdout=sp.PIPE, stderr=sp.PIPE) + stdout, stderr = proc.communicate() + + t = threading.Thread(target=run_process) + t.start() + t0 = time.time() + time.sleep(1) + assert proc, "process didn't start?" + + # Send the running script a ctrl-c before the second attempt is made + proc.send_signal(signal.SIGINT) + proc.wait() + t1 = time.time() + + # Check that we didn't try the second attempt + assert t1 - t0 < 2.5 + assert proc.returncode != 0 + if sys.implementation.name != "pypy": # unexpected, but hey. + assert "KeyboardInterrupt" in stderr + assert stdout == ""