]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: don't process further attempt if ctrl-c is pressed during connection
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 7 May 2025 21:39:20 +0000 (23:39 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 7 May 2025 23:14:15 +0000 (01:14 +0200)
See for example #1055 where this behaviour is reported, #1076 where it
is further discussed.

docs/news.rst
psycopg/psycopg/connection.py
psycopg/psycopg/connection_async.py
tests/test_concurrency.py

index 1238607fa44ff0241d22fc88d2b280662c909bb4..d73b5ac46a279a90f8209bb78f2414a54b11fb66 100644 (file)
@@ -21,6 +21,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`).
 
 
 Current release
index e1e4eeefce0e82aecdeaea05ec0a22bec6bac26e..112f0db1d5b4bb7448249e4e37bee2a864e01873 100644 (file)
@@ -99,7 +99,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",
@@ -109,6 +109,8 @@ class Connection(BaseConnection[Row]):
                         str(ex),
                     )
                 last_ex = ex
+            except e._NO_TRACEBACK as ex:
+                raise ex.with_traceback(None)
             else:
                 break
 
index f5e95e4aa8ef612570e77e54d6d4e0248f04672a..33cd4cffc34c1f735f3c165929924874172f0872 100644 (file)
@@ -117,7 +117,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",
@@ -127,6 +127,8 @@ class AsyncConnection(BaseConnection[Row]):
                         str(ex),
                     )
                 last_ex = ex
+            except e._NO_TRACEBACK as ex:
+                raise ex.with_traceback(None)
             else:
                 break
 
index 488b197c01bba6fa4160b61a785225c4c494678f..4b5b760c8731a82af2658b6cb5fb6285e42043c8 100644 (file)
@@ -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 == ""