Skip to content

Commit be24573

Browse files
committed
PYTHON-5672 Add coverage for _PoolCheckout leak guard; remove dead server.checkout()
server.checkout() was a thin wrapper around pool.checkout() that nothing called after _ClientCheckout was changed to call server.pool.checkout() directly. Add a test that mocks publish_connection_checked_out to raise and verifies the connection is returned to the pool rather than leaked.
1 parent 65d0cbe commit be24573

4 files changed

Lines changed: 58 additions & 8 deletions

File tree

pymongo/asynchronous/server.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from bson.objectid import ObjectId
4444
from pymongo.asynchronous.mongo_client import AsyncMongoClient
4545
from pymongo.asynchronous.monitor import Monitor
46-
from pymongo.asynchronous.pool import AsyncConnection, Pool, _PoolCheckout
46+
from pymongo.asynchronous.pool import AsyncConnection, Pool
4747
from pymongo.monitoring import _EventListeners
4848
from pymongo.read_preferences import _ServerMode
4949
from pymongo.server_description import ServerDescription
@@ -226,9 +226,6 @@ async def run_operation(
226226

227227
return response
228228

229-
def checkout(self) -> _PoolCheckout:
230-
return self.pool.checkout()
231-
232229
@property
233230
def description(self) -> ServerDescription:
234231
return self._description

pymongo/synchronous/server.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
from pymongo.server_description import ServerDescription
4747
from pymongo.synchronous.mongo_client import MongoClient
4848
from pymongo.synchronous.monitor import Monitor
49-
from pymongo.synchronous.pool import Connection, Pool, _PoolCheckout
49+
from pymongo.synchronous.pool import Connection, Pool
5050
from pymongo.typings import _DocumentOut
5151

5252
_IS_SYNC = True
@@ -226,9 +226,6 @@ def run_operation(
226226

227227
return response
228228

229-
def checkout(self) -> _PoolCheckout:
230-
return self.pool.checkout()
231-
232229
@property
233230
def description(self) -> ServerDescription:
234231
return self._description

test/asynchronous/test_pooling.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,34 @@ async def test_get_socket_and_exception(self):
215215

216216
self.assertEqual(1, len(cx_pool.conns))
217217

218+
async def test_checkout_event_listener_failure_no_leak(self):
219+
# Connection is returned to the pool when publish_connection_checked_out raises.
220+
from unittest.mock import patch
221+
222+
from pymongo.monitoring import _EventListeners
223+
from test.utils_shared import CMAPListener
224+
225+
cx_pool = await self.create_pool(
226+
max_pool_size=1, event_listeners=_EventListeners([CMAPListener()])
227+
)
228+
229+
with patch.object(
230+
cx_pool.opts._event_listeners,
231+
"publish_connection_checked_out",
232+
side_effect=RuntimeError("simulated failure"),
233+
):
234+
with self.assertRaises(RuntimeError):
235+
async with cx_pool.checkout():
236+
pass
237+
238+
# Connection was returned to the pool — not leaked.
239+
self.assertEqual(1, len(cx_pool.conns))
240+
self.assertEqual(0, cx_pool.active_sockets)
241+
242+
# Pool is still functional.
243+
async with cx_pool.checkout():
244+
pass
245+
218246
async def test_pool_removes_closed_socket(self):
219247
# Test that Pool removes explicitly closed socket.
220248
cx_pool = await self.create_pool()

test/test_pooling.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,34 @@ def test_get_socket_and_exception(self):
215215

216216
self.assertEqual(1, len(cx_pool.conns))
217217

218+
def test_checkout_event_listener_failure_no_leak(self):
219+
# Connection is returned to the pool when publish_connection_checked_out raises.
220+
from unittest.mock import patch
221+
222+
from pymongo.monitoring import _EventListeners
223+
from test.utils_shared import CMAPListener
224+
225+
cx_pool = self.create_pool(
226+
max_pool_size=1, event_listeners=_EventListeners([CMAPListener()])
227+
)
228+
229+
with patch.object(
230+
cx_pool.opts._event_listeners,
231+
"publish_connection_checked_out",
232+
side_effect=RuntimeError("simulated failure"),
233+
):
234+
with self.assertRaises(RuntimeError):
235+
with cx_pool.checkout():
236+
pass
237+
238+
# Connection was returned to the pool — not leaked.
239+
self.assertEqual(1, len(cx_pool.conns))
240+
self.assertEqual(0, cx_pool.active_sockets)
241+
242+
# Pool is still functional.
243+
with cx_pool.checkout():
244+
pass
245+
218246
def test_pool_removes_closed_socket(self):
219247
# Test that Pool removes explicitly closed socket.
220248
cx_pool = self.create_pool()

0 commit comments

Comments
 (0)