Skip to content

Commit a30f5ad

Browse files
committed
fix: remove inactivity timeout in psycopg proxy to preserve idle connections
1 parent ab1d954 commit a30f5ad

2 files changed

Lines changed: 28 additions & 54 deletions

File tree

‎google/cloud/sql/connector/psycopg.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,7 @@ def forward_pending() -> bool:
7171
if forward_pending():
7272
break
7373

74-
events = sel.select(timeout=30)
75-
if not events:
76-
logger.debug("psycopg proxy: inactivity timeout (30s)")
77-
break
74+
events = sel.select()
7875

7976
for key, mask in events:
8077
if key.data == "local":

‎tests/unit/test_psycopg.py‎

Lines changed: 27 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@
2020
from unittest.mock import MagicMock
2121
from unittest.mock import patch
2222

23+
import pytest
24+
2325
from google.cloud.sql.connector.psycopg import _proxy
2426
from google.cloud.sql.connector.psycopg import connect
2527

28+
pytestmark = pytest.mark.skipif(
29+
not hasattr(socket, "AF_UNIX"),
30+
reason="Unix domain sockets (AF_UNIX) not available on this platform",
31+
)
32+
2633

2734
class MockableSocket(socket.socket):
2835
pass
@@ -170,40 +177,13 @@ def mock_connect_impl(*args: Any, **kwargs: Any) -> MagicMock:
170177
assert not os.path.exists(kwargs["host"])
171178

172179

173-
def test_proxy_timeout() -> None:
174-
"""Test that _proxy exits and cleans up on selectors timeout."""
175-
local_client, local_server = socket.socketpair()
176-
remote_client, remote_server = socket.socketpair()
177-
178-
# Mock selectors.DefaultSelector.select to return empty list (timeout)
179-
with patch(
180-
"google.cloud.sql.connector.psycopg.selectors.DefaultSelector"
181-
) as mock_selector_cls:
182-
mock_selector = MagicMock()
183-
mock_selector.select.return_value = [] # Timeout
184-
mock_selector_cls.return_value = mock_selector
185-
186-
# Run proxy
187-
_proxy(local_server, remote_client)
188-
189-
# Sockets should be closed
190-
assert local_server.fileno() == -1
191-
assert remote_client.fileno() == -1
192-
193-
# Clean up outer sockets
194-
local_client.close()
195-
remote_server.close()
196-
197-
198180
@patch("psycopg.connect")
199181
def test_connect_wrapper_failure(mock_psycopg_connect: MagicMock) -> None:
200182
"""Test that connect wrapper cleans up correctly when psycopg.connect fails."""
201183
mock_remote_sock = MagicMock(spec=ssl.SSLSocket)
202184
mock_psycopg_connect.side_effect = Exception("connection failed simulated")
203185

204186
# Call the connect wrapper and expect it to raise
205-
import pytest
206-
207187
with pytest.raises(Exception, match="connection failed simulated"):
208188
connect(
209189
"127.0.0.1",
@@ -217,18 +197,16 @@ def test_connect_wrapper_failure(mock_psycopg_connect: MagicMock) -> None:
217197
assert mock_remote_sock.close.called
218198

219199
# Verify cleanup with mocked paths
220-
with patch(
221-
"google.cloud.sql.connector.psycopg.tempfile.mkdtemp"
222-
) as mock_mkdtemp:
200+
with patch("google.cloud.sql.connector.psycopg.tempfile.mkdtemp") as mock_mkdtemp:
223201
mock_mkdtemp.return_value = "/tmp/mock_temp_dir_failure"
224202

225-
with patch(
226-
"google.cloud.sql.connector.psycopg.os.rmdir"
227-
) as mock_rmdir, patch(
228-
"google.cloud.sql.connector.psycopg.os.remove"
229-
) as mock_remove, patch(
230-
"google.cloud.sql.connector.psycopg.socket.socket"
231-
) as mock_socket_cls:
203+
with (
204+
patch("google.cloud.sql.connector.psycopg.os.rmdir") as mock_rmdir,
205+
patch("google.cloud.sql.connector.psycopg.os.remove") as mock_remove,
206+
patch(
207+
"google.cloud.sql.connector.psycopg.socket.socket"
208+
) as mock_socket_cls,
209+
):
232210
# Mock the local socket to avoid real OS bind/listen
233211
mock_local_sock = MagicMock()
234212
mock_socket_cls.return_value = mock_local_sock
@@ -389,9 +367,7 @@ def test_proxy_pending_local_send_error() -> None:
389367
remote_client.recv = MagicMock(return_value=b"pending data")
390368

391369
# Mock local_server.sendall to raise OSError
392-
local_server.sendall = MagicMock(
393-
side_effect=OSError("local send pending failed")
394-
)
370+
local_server.sendall = MagicMock(side_effect=OSError("local send pending failed"))
395371

396372
# Run proxy
397373
_proxy(local_server, remote_client)
@@ -408,8 +384,6 @@ def test_connect_import_error() -> None:
408384
"""Test that connect raises ImportError if psycopg is not installed."""
409385
mock_remote_sock = MagicMock(spec=ssl.SSLSocket)
410386

411-
import pytest
412-
413387
with pytest.raises(ImportError, match='Unable to import module "psycopg."'):
414388
connect("127.0.0.1", mock_remote_sock)
415389

@@ -426,13 +400,17 @@ def mock_connect_impl(*args: Any, **kwargs: Any) -> MagicMock:
426400
client.close()
427401
return MagicMock()
428402

429-
with patch("psycopg.connect", side_effect=mock_connect_impl), patch(
430-
"google.cloud.sql.connector.psycopg.os.remove",
431-
side_effect=OSError("remove failed"),
432-
) as mock_remove, patch(
433-
"google.cloud.sql.connector.psycopg.os.rmdir",
434-
side_effect=OSError("rmdir failed"),
435-
) as mock_rmdir:
403+
with (
404+
patch("psycopg.connect", side_effect=mock_connect_impl),
405+
patch(
406+
"google.cloud.sql.connector.psycopg.os.remove",
407+
side_effect=OSError("remove failed"),
408+
) as mock_remove,
409+
patch(
410+
"google.cloud.sql.connector.psycopg.os.rmdir",
411+
side_effect=OSError("rmdir failed"),
412+
) as mock_rmdir,
413+
):
436414
conn = connect(
437415
"127.0.0.1",
438416
mock_remote_sock,
@@ -444,4 +422,3 @@ def mock_connect_impl(*args: Any, **kwargs: Any) -> MagicMock:
444422
assert conn is not None
445423
assert mock_remove.called
446424
assert mock_rmdir.called
447-

0 commit comments

Comments
 (0)