]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: don't process further attempt if ctrl-c is pressed during connection 1077/head
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:12:19 +0000 (01:12 +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 b2446fa1ce9769e10d43e57f95ad85a0891486c0..e40383436472f6901b3d89e5eef3bb08549a2c57 100644 (file)
@@ -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
index 156ff82ff025ad402b64c7922268a1f31ef02ad5..8ee5128ed8ecec0a6ea511e225f187c863aa5747 100644 (file)
@@ -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
 
index a2397ed2f95ef851888a499a23b90c85992c1677..ddfccae1aff3aba7b4337c1d9d6c9df578e0f916 100644 (file)
@@ -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
 
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 == ""