Skip to content

Commit 1043a1f

Browse files
fix: throw exception when connecting to closed Connector (#436)
1 parent 5e1fa00 commit 1043a1f

File tree

5 files changed

+66
-5
lines changed

5 files changed

+66
-5
lines changed

google/cloud/alloydb/connector/async_connector.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from google.cloud.alloydb.connector.client import AlloyDBClient
2828
from google.cloud.alloydb.connector.enums import IPTypes
2929
from google.cloud.alloydb.connector.enums import RefreshStrategy
30+
from google.cloud.alloydb.connector.exceptions import ClosedConnectorError
3031
from google.cloud.alloydb.connector.instance import RefreshAheadCache
3132
from google.cloud.alloydb.connector.lazy import LazyRefreshCache
3233
from google.cloud.alloydb.connector.types import CacheTypes
@@ -102,6 +103,7 @@ def __init__(
102103
except RuntimeError:
103104
self._keys = None
104105
self._client: Optional[AlloyDBClient] = None
106+
self._closed = False
105107

106108
async def connect(
107109
self,
@@ -127,6 +129,10 @@ async def connect(
127129
Returns:
128130
connection: A DBAPI connection to the specified AlloyDB instance.
129131
"""
132+
if self._closed:
133+
raise ClosedConnectorError(
134+
"Connection attempt failed because the connector has already been closed."
135+
)
130136
if self._keys is None:
131137
self._keys = asyncio.create_task(generate_keys())
132138
if self._client is None:
@@ -236,3 +242,4 @@ async def close(self) -> None:
236242
"""Helper function to cancel RefreshAheadCaches' tasks
237243
and close client."""
238244
await asyncio.gather(*[cache.close() for cache in self._cache.values()])
245+
self._closed = True

google/cloud/alloydb/connector/connector.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from google.cloud.alloydb.connector.client import AlloyDBClient
3333
from google.cloud.alloydb.connector.enums import IPTypes
3434
from google.cloud.alloydb.connector.enums import RefreshStrategy
35+
from google.cloud.alloydb.connector.exceptions import ClosedConnectorError
3536
from google.cloud.alloydb.connector.instance import RefreshAheadCache
3637
from google.cloud.alloydb.connector.lazy import LazyRefreshCache
3738
import google.cloud.alloydb.connector.pg8000 as pg8000
@@ -124,6 +125,7 @@ def __init__(
124125
)
125126
self._client: Optional[AlloyDBClient] = None
126127
self._static_conn_info = static_conn_info
128+
self._closed = False
127129

128130
def connect(self, instance_uri: str, driver: str, **kwargs: Any) -> Any:
129131
"""
@@ -144,6 +146,10 @@ def connect(self, instance_uri: str, driver: str, **kwargs: Any) -> Any:
144146
Returns:
145147
connection: A DBAPI connection to the specified AlloyDB instance.
146148
"""
149+
if self._closed:
150+
raise ClosedConnectorError(
151+
"Connection attempt failed because the connector has already been closed."
152+
)
147153
# call async connect and wait on result
148154
connect_task = asyncio.run_coroutine_threadsafe(
149155
self.connect_async(instance_uri, driver, **kwargs), self._loop
@@ -385,6 +391,7 @@ def close(self) -> None:
385391
self._loop.call_soon_threadsafe(self._loop.stop)
386392
# wait for thread to finish closing (i.e. loop to stop)
387393
self._thread.join()
394+
self._closed = True
388395

389396
async def close_async(self) -> None:
390397
"""Helper function to cancel RefreshAheadCaches' tasks

google/cloud/alloydb/connector/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ class RefreshError(Exception):
1919

2020
class IPTypeNotFoundError(Exception):
2121
pass
22+
23+
24+
class ClosedConnectorError(Exception):
25+
pass

tests/unit/test_async_connector.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from google.cloud.alloydb.connector import AsyncConnector
2626
from google.cloud.alloydb.connector import IPTypes
27+
from google.cloud.alloydb.connector.exceptions import ClosedConnectorError
2728
from google.cloud.alloydb.connector.exceptions import IPTypeNotFoundError
2829
from google.cloud.alloydb.connector.instance import RefreshAheadCache
2930

@@ -42,6 +43,7 @@ async def test_AsyncConnector_init(credentials: FakeCredentials) -> None:
4243
assert connector._client is None
4344
assert connector._credentials == credentials
4445
assert connector._enable_iam_auth is False
46+
assert connector._closed is False
4547
await connector.close()
4648

4749

@@ -109,7 +111,7 @@ async def test_AsyncConnector_init_bad_ip_type(credentials: FakeCredentials) ->
109111
)
110112

111113

112-
def test_AsyncConnector_init_alloydb_api_endpoint_with_http_prefix(
114+
async def test_AsyncConnector_init_alloydb_api_endpoint_with_http_prefix(
113115
credentials: FakeCredentials,
114116
) -> None:
115117
"""
@@ -120,10 +122,10 @@ def test_AsyncConnector_init_alloydb_api_endpoint_with_http_prefix(
120122
alloydb_api_endpoint="http://alloydb.googleapis.com", credentials=credentials
121123
)
122124
assert connector._alloydb_api_endpoint == "alloydb.googleapis.com"
123-
connector.close()
125+
await connector.close()
124126

125127

126-
def test_AsyncConnector_init_alloydb_api_endpoint_with_https_prefix(
128+
async def test_AsyncConnector_init_alloydb_api_endpoint_with_https_prefix(
127129
credentials: FakeCredentials,
128130
) -> None:
129131
"""
@@ -134,7 +136,7 @@ def test_AsyncConnector_init_alloydb_api_endpoint_with_https_prefix(
134136
alloydb_api_endpoint="https://alloydb.googleapis.com", credentials=credentials
135137
)
136138
assert connector._alloydb_api_endpoint == "alloydb.googleapis.com"
137-
connector.close()
139+
await connector.close()
138140

139141

140142
@pytest.mark.asyncio
@@ -357,3 +359,26 @@ async def test_Connector_remove_cached_no_ip_type(credentials: FakeCredentials)
357359
await connector.connect(instance_uri, "asyncpg", ip_type="private")
358360
# check that cache has been removed from dict
359361
assert instance_uri not in connector._cache
362+
363+
364+
async def test_close_sets_connector_as_closed(credentials: FakeCredentials) -> None:
365+
"""
366+
Test that when connector is closed, it marked as closed.
367+
"""
368+
async with AsyncConnector(credentials=credentials) as connector:
369+
assert connector._closed is False
370+
assert connector._closed is True
371+
372+
373+
async def test_connect_when_closed(credentials: FakeCredentials) -> None:
374+
"""
375+
Test that connector.connect errors when the connection is closed.
376+
"""
377+
connector = AsyncConnector(credentials=credentials)
378+
await connector.close()
379+
with pytest.raises(ClosedConnectorError) as exc_info:
380+
await connector.connect("", "")
381+
assert (
382+
exc_info.value.args[0]
383+
== "Connection attempt failed because the connector has already been closed."
384+
)

tests/unit/test_connector.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from google.cloud.alloydb.connector import Connector
2727
from google.cloud.alloydb.connector import IPTypes
28+
from google.cloud.alloydb.connector.exceptions import ClosedConnectorError
2829
from google.cloud.alloydb.connector.exceptions import IPTypeNotFoundError
2930
from google.cloud.alloydb.connector.instance import RefreshAheadCache
3031
from google.cloud.alloydb.connector.utils import generate_keys
@@ -40,6 +41,7 @@ def test_Connector_init(credentials: FakeCredentials) -> None:
4041
assert connector._alloydb_api_endpoint == "alloydb.googleapis.com"
4142
assert connector._client is None
4243
assert connector._credentials == credentials
44+
assert connector._closed is False
4345
connector.close()
4446

4547

@@ -150,15 +152,17 @@ def test_Connector_context_manager(credentials: FakeCredentials) -> None:
150152
def test_Connector_close(credentials: FakeCredentials) -> None:
151153
"""
152154
Test that Connector's close method stops event loop and
153-
background thread.
155+
background thread, and sets the connector as closed.
154156
"""
155157
with Connector(credentials) as connector:
156158
loop: asyncio.AbstractEventLoop = connector._loop
157159
thread: Thread = connector._thread
158160
assert loop.is_running() is True
159161
assert thread.is_alive() is True
162+
assert connector._closed is False
160163
assert loop.is_running() is False
161164
assert thread.is_alive() is False
165+
assert connector._closed is True
162166

163167

164168
@pytest.mark.usefixtures("proxy_server")
@@ -302,3 +306,17 @@ def test_Connector_static_connection_info(
302306
)
303307
# check connection is returned
304308
assert connection is True
309+
310+
311+
def test_connect_when_closed(credentials: FakeCredentials) -> None:
312+
"""
313+
Test that connector.connect errors when the connection is closed.
314+
"""
315+
connector = Connector(credentials=credentials)
316+
connector.close()
317+
with pytest.raises(ClosedConnectorError) as exc_info:
318+
connector.connect("", "")
319+
assert (
320+
exc_info.value.args[0]
321+
== "Connection attempt failed because the connector has already been closed."
322+
)

0 commit comments

Comments
 (0)