Skip to content

Commit ef4a49d

Browse files
committed
Close all and counts
1 parent fc33375 commit ef4a49d

File tree

5 files changed

+173
-15
lines changed

5 files changed

+173
-15
lines changed

adafruit_connection_manager.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ def create_fake_ssl_context(
9999
return _FakeSSLContext(iface)
100100

101101

102-
_global_socketpool = {}
102+
_global_connection_managers = {}
103+
_global_socketpools = {}
103104
_global_ssl_contexts = {}
104105

105106

@@ -113,7 +114,7 @@ def get_radio_socketpool(radio):
113114
* Using a WIZ5500 (Like the Adafruit Ethernet FeatherWing)
114115
"""
115116
class_name = radio.__class__.__name__
116-
if class_name not in _global_socketpool:
117+
if class_name not in _global_socketpools:
117118
if class_name == "Radio":
118119
import ssl # pylint: disable=import-outside-toplevel
119120

@@ -151,10 +152,10 @@ def get_radio_socketpool(radio):
151152
else:
152153
raise AttributeError(f"Unsupported radio class: {class_name}")
153154

154-
_global_socketpool[class_name] = pool
155+
_global_socketpools[class_name] = pool
155156
_global_ssl_contexts[class_name] = ssl_context
156157

157-
return _global_socketpool[class_name]
158+
return _global_socketpools[class_name]
158159

159160

160161
def get_radio_ssl_context(radio):
@@ -186,10 +187,10 @@ def __init__(
186187
self._available_socket = {}
187188
self._open_sockets = {}
188189

189-
def _free_sockets(self) -> None:
190+
def _free_sockets(self, force: bool = False) -> None:
190191
available_sockets = []
191192
for socket, free in self._available_socket.items():
192-
if free:
193+
if free or force:
193194
available_sockets.append(socket)
194195

195196
for socket in available_sockets:
@@ -203,6 +204,18 @@ def _get_key_for_socket(self, socket):
203204
except StopIteration:
204205
return None
205206

207+
@property
208+
def open_sockets(self) -> int:
209+
"""Get the count of open sockets"""
210+
return len(self._open_sockets)
211+
212+
@property
213+
def freeable_open_sockets(self) -> int:
214+
"""Get the count of freeable open sockets"""
215+
return len(
216+
[socket for socket, free in self._available_socket.items() if free is True]
217+
)
218+
206219
def close_socket(self, socket: SocketType) -> None:
207220
"""Close a previously opened socket."""
208221
if socket not in self._open_sockets.values():
@@ -306,11 +319,25 @@ def get_socket(
306319
# global helpers
307320

308321

309-
_global_connection_manager = {}
322+
def connection_manager_close_all(
323+
socket_pool: Optional[SocketpoolModuleType] = None,
324+
) -> None:
325+
"""Close all open sockets for pool"""
326+
if socket_pool:
327+
keys = [socket_pool]
328+
else:
329+
keys = _global_connection_managers.keys()
330+
331+
for key in keys:
332+
connection_manager = _global_connection_managers.get(key, None)
333+
if connection_manager is None:
334+
raise RuntimeError("SocketPool not managed")
335+
336+
connection_manager._free_sockets(force=True) # pylint: disable=protected-access
310337

311338

312339
def get_connection_manager(socket_pool: SocketpoolModuleType) -> ConnectionManager:
313340
"""Get the ConnectionManager singleton for the given pool"""
314-
if socket_pool not in _global_connection_manager:
315-
_global_connection_manager[socket_pool] = ConnectionManager(socket_pool)
316-
return _global_connection_manager[socket_pool]
341+
if socket_pool not in _global_connection_managers:
342+
_global_connection_managers[socket_pool] = ConnectionManager(socket_pool)
343+
return _global_connection_managers[socket_pool]

examples/connectionmanager_helpers.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,38 @@
2424

2525
# get request session
2626
requests = adafruit_requests.Session(pool, ssl_context)
27+
connection_manager = adafruit_connection_manager.get_connection_manager(pool)
28+
print("-" * 40)
29+
print("Nothing yet opened")
30+
print(f"Open Sockets: {connection_manager.open_sockets}")
31+
print(f"Freeable Open Sockets: {connection_manager.freeable_open_sockets}")
2732

2833
# make request
2934
print("-" * 40)
30-
print(f"Fetching from {TEXT_URL}")
35+
print(f"Fetching from {TEXT_URL} in a context handler")
36+
with requests.get(TEXT_URL) as response:
37+
response_text = response.text
38+
print(f"Text Response {response_text}")
39+
40+
print("-" * 40)
41+
print("1 request, opened and freed")
42+
print(f"Open Sockets: {connection_manager.open_sockets}")
43+
print(f"Freeable Open Sockets: {connection_manager.freeable_open_sockets}")
3144

45+
print("-" * 40)
46+
print(f"Fetching from {TEXT_URL} not in a context handler")
3247
response = requests.get(TEXT_URL)
33-
response_text = response.text
34-
response.close()
3548

36-
print(f"Text Response {response_text}")
3749
print("-" * 40)
50+
print("1 request, opened but not freed")
51+
print(f"Open Sockets: {connection_manager.open_sockets}")
52+
print(f"Freeable Open Sockets: {connection_manager.freeable_open_sockets}")
53+
54+
print("-" * 40)
55+
print("Closing everything in the pool")
56+
adafruit_connection_manager.connection_manager_close_all(pool)
57+
58+
print("-" * 40)
59+
print("Everything closed")
60+
print(f"Open Sockets: {connection_manager.open_sockets}")
61+
print(f"Freeable Open Sockets: {connection_manager.freeable_open_sockets}")

tests/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ def adafruit_wiznet5k_with_ssl_socket_module():
6565
@pytest.fixture(autouse=True)
6666
def reset_connection_manager(monkeypatch):
6767
monkeypatch.setattr(
68-
"adafruit_connection_manager._global_socketpool",
68+
"adafruit_connection_manager._global_connection_managers",
69+
{},
70+
)
71+
monkeypatch.setattr(
72+
"adafruit_connection_manager._global_socketpools",
6973
{},
7074
)
7175
monkeypatch.setattr(
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
5+
""" Get Connection Manager Tests """
6+
7+
import mocket
8+
import pytest
9+
10+
import adafruit_connection_manager
11+
12+
13+
def test_connection_manager_close_all_all():
14+
mock_pool_1 = mocket.MocketPool()
15+
mock_pool_2 = mocket.MocketPool()
16+
assert mock_pool_1 != mock_pool_2
17+
18+
connection_manager_1 = adafruit_connection_manager.get_connection_manager(
19+
mock_pool_1
20+
)
21+
assert connection_manager_1.open_sockets == 0
22+
assert connection_manager_1.freeable_open_sockets == 0
23+
connection_manager_2 = adafruit_connection_manager.get_connection_manager(
24+
mock_pool_2
25+
)
26+
assert connection_manager_2.open_sockets == 0
27+
assert connection_manager_2.freeable_open_sockets == 0
28+
assert len(adafruit_connection_manager._global_connection_managers) == 2
29+
30+
socket_1 = connection_manager_1.get_socket(mocket.MOCK_HOST_1, 80, "http:")
31+
assert connection_manager_1.open_sockets == 1
32+
assert connection_manager_1.freeable_open_sockets == 0
33+
assert connection_manager_2.open_sockets == 0
34+
assert connection_manager_2.freeable_open_sockets == 0
35+
socket_2 = connection_manager_2.get_socket(mocket.MOCK_HOST_1, 80, "http:")
36+
assert connection_manager_2.open_sockets == 1
37+
assert connection_manager_2.freeable_open_sockets == 0
38+
39+
adafruit_connection_manager.connection_manager_close_all()
40+
assert connection_manager_1.open_sockets == 0
41+
assert connection_manager_1.freeable_open_sockets == 0
42+
assert connection_manager_2.open_sockets == 0
43+
assert connection_manager_2.freeable_open_sockets == 0
44+
socket_1.close.assert_called_once()
45+
socket_2.close.assert_called_once()
46+
47+
48+
def test_connection_manager_close_all_single():
49+
mock_pool_1 = mocket.MocketPool()
50+
mock_pool_2 = mocket.MocketPool()
51+
assert mock_pool_1 != mock_pool_2
52+
53+
connection_manager_1 = adafruit_connection_manager.get_connection_manager(
54+
mock_pool_1
55+
)
56+
assert connection_manager_1.open_sockets == 0
57+
assert connection_manager_1.freeable_open_sockets == 0
58+
connection_manager_2 = adafruit_connection_manager.get_connection_manager(
59+
mock_pool_2
60+
)
61+
assert connection_manager_2.open_sockets == 0
62+
assert connection_manager_2.freeable_open_sockets == 0
63+
assert len(adafruit_connection_manager._global_connection_managers) == 2
64+
65+
socket_1 = connection_manager_1.get_socket(mocket.MOCK_HOST_1, 80, "http:")
66+
assert connection_manager_1.open_sockets == 1
67+
assert connection_manager_1.freeable_open_sockets == 0
68+
assert connection_manager_2.open_sockets == 0
69+
assert connection_manager_2.freeable_open_sockets == 0
70+
socket_2 = connection_manager_2.get_socket(mocket.MOCK_HOST_1, 80, "http:")
71+
assert connection_manager_2.open_sockets == 1
72+
assert connection_manager_2.freeable_open_sockets == 0
73+
74+
adafruit_connection_manager.connection_manager_close_all(mock_pool_1)
75+
assert connection_manager_1.open_sockets == 0
76+
assert connection_manager_1.freeable_open_sockets == 0
77+
assert connection_manager_2.open_sockets == 1
78+
assert connection_manager_2.freeable_open_sockets == 0
79+
socket_1.close.assert_called_once()
80+
socket_2.close.assert_not_called()
81+
82+
83+
def test_connection_manager_close_all_untracked():
84+
mock_pool_1 = mocket.MocketPool()
85+
with pytest.raises(RuntimeError) as context:
86+
adafruit_connection_manager.connection_manager_close_all(mock_pool_1)
87+
assert "SocketPool not managed" in str(context)

tests/free_socket_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def test_free_socket():
1616
mock_pool.socket.return_value = mock_socket_1
1717

1818
connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool)
19+
assert connection_manager.open_sockets == 0
20+
assert connection_manager.freeable_open_sockets == 0
1921

2022
# validate socket is tracked and not available
2123
socket = connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:")
@@ -24,12 +26,16 @@ def test_free_socket():
2426
assert socket in connection_manager._available_socket
2527
assert connection_manager._available_socket[socket] is False
2628
assert key in connection_manager._open_sockets
29+
assert connection_manager.open_sockets == 1
30+
assert connection_manager.freeable_open_sockets == 0
2731

2832
# validate socket is tracked and is available
2933
connection_manager.free_socket(socket)
3034
assert socket in connection_manager._available_socket
3135
assert connection_manager._available_socket[socket] is True
3236
assert key in connection_manager._open_sockets
37+
assert connection_manager.open_sockets == 1
38+
assert connection_manager.freeable_open_sockets == 1
3339

3440

3541
def test_free_socket_not_managed():
@@ -54,26 +60,36 @@ def test_free_sockets():
5460
]
5561

5662
connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool)
63+
assert connection_manager.open_sockets == 0
64+
assert connection_manager.freeable_open_sockets == 0
5765

5866
# validate socket is tracked and not available
5967
socket_1 = connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:")
6068
assert socket_1 == mock_socket_1
6169
assert socket_1 in connection_manager._available_socket
6270
assert connection_manager._available_socket[socket_1] is False
71+
assert connection_manager.open_sockets == 1
72+
assert connection_manager.freeable_open_sockets == 0
6373

6474
socket_2 = connection_manager.get_socket(mocket.MOCK_HOST_2, 80, "http:")
6575
assert socket_2 == mock_socket_2
76+
assert connection_manager.open_sockets == 2
77+
assert connection_manager.freeable_open_sockets == 0
6678

6779
# validate socket is tracked and is available
6880
connection_manager.free_socket(socket_1)
6981
assert socket_1 in connection_manager._available_socket
7082
assert connection_manager._available_socket[socket_1] is True
83+
assert connection_manager.open_sockets == 2
84+
assert connection_manager.freeable_open_sockets == 1
7185

7286
# validate socket is no longer tracked
7387
connection_manager._free_sockets()
7488
assert socket_1 not in connection_manager._available_socket
7589
assert socket_2 in connection_manager._available_socket
7690
mock_socket_1.close.assert_called_once()
91+
assert connection_manager.open_sockets == 1
92+
assert connection_manager.freeable_open_sockets == 0
7793

7894

7995
def test_get_key_for_socket():

0 commit comments

Comments
 (0)