Skip to content

Commit c136684

Browse files
authored
PYTHON-4585 Cursor.to_list does not apply client's timeoutMS setting (#1860)
1 parent 40ebc16 commit c136684

File tree

6 files changed

+78
-4
lines changed

6 files changed

+78
-4
lines changed

pymongo/asynchronous/command_cursor.py

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
)
3030

3131
from bson import CodecOptions, _convert_raw_document_lists_to_streams
32+
from pymongo import _csot
3233
from pymongo.asynchronous.cursor import _ConnectionManager
3334
from pymongo.cursor_shared import _CURSOR_CLOSED_ERRORS
3435
from pymongo.errors import ConnectionFailure, InvalidOperation, OperationFailure
@@ -77,6 +78,7 @@ def __init__(
7778
self._address = address
7879
self._batch_size = batch_size
7980
self._max_await_time_ms = max_await_time_ms
81+
self._timeout = self._collection.database.client.options.timeout
8082
self._session = session
8183
self._explicit_session = explicit_session
8284
self._killed = self._id == 0
@@ -385,6 +387,7 @@ async def __aenter__(self) -> AsyncCommandCursor[_DocumentType]:
385387
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
386388
await self.close()
387389

390+
@_csot.apply
388391
async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]:
389392
"""Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``.
390393

pymongo/asynchronous/cursor.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from bson import RE_TYPE, _convert_raw_document_lists_to_streams
3737
from bson.code import Code
3838
from bson.son import SON
39-
from pymongo import helpers_shared
39+
from pymongo import _csot, helpers_shared
4040
from pymongo.asynchronous.helpers import anext
4141
from pymongo.collation import validate_collation_or_none
4242
from pymongo.common import (
@@ -196,6 +196,7 @@ def __init__(
196196
self._explain = False
197197
self._comment = comment
198198
self._max_time_ms = max_time_ms
199+
self._timeout = self._collection.database.client.options.timeout
199200
self._max_await_time_ms: Optional[int] = None
200201
self._max: Optional[Union[dict[Any, Any], _Sort]] = max
201202
self._min: Optional[Union[dict[Any, Any], _Sort]] = min
@@ -1290,6 +1291,7 @@ async def __aenter__(self) -> AsyncCursor[_DocumentType]:
12901291
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
12911292
await self.close()
12921293

1294+
@_csot.apply
12931295
async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]:
12941296
"""Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``.
12951297

pymongo/synchronous/command_cursor.py

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
)
3030

3131
from bson import CodecOptions, _convert_raw_document_lists_to_streams
32+
from pymongo import _csot
3233
from pymongo.cursor_shared import _CURSOR_CLOSED_ERRORS
3334
from pymongo.errors import ConnectionFailure, InvalidOperation, OperationFailure
3435
from pymongo.message import (
@@ -77,6 +78,7 @@ def __init__(
7778
self._address = address
7879
self._batch_size = batch_size
7980
self._max_await_time_ms = max_await_time_ms
81+
self._timeout = self._collection.database.client.options.timeout
8082
self._session = session
8183
self._explicit_session = explicit_session
8284
self._killed = self._id == 0
@@ -385,6 +387,7 @@ def __enter__(self) -> CommandCursor[_DocumentType]:
385387
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
386388
self.close()
387389

390+
@_csot.apply
388391
def to_list(self, length: Optional[int] = None) -> list[_DocumentType]:
389392
"""Converts the contents of this cursor to a list more efficiently than ``[doc for doc in cursor]``.
390393

pymongo/synchronous/cursor.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from bson import RE_TYPE, _convert_raw_document_lists_to_streams
3737
from bson.code import Code
3838
from bson.son import SON
39-
from pymongo import helpers_shared
39+
from pymongo import _csot, helpers_shared
4040
from pymongo.collation import validate_collation_or_none
4141
from pymongo.common import (
4242
validate_is_document_type,
@@ -196,6 +196,7 @@ def __init__(
196196
self._explain = False
197197
self._comment = comment
198198
self._max_time_ms = max_time_ms
199+
self._timeout = self._collection.database.client.options.timeout
199200
self._max_await_time_ms: Optional[int] = None
200201
self._max: Optional[Union[dict[Any, Any], _Sort]] = max
201202
self._min: Optional[Union[dict[Any, Any], _Sort]] = min
@@ -1288,6 +1289,7 @@ def __enter__(self) -> Cursor[_DocumentType]:
12881289
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
12891290
self.close()
12901291

1292+
@_csot.apply
12911293
def to_list(self, length: Optional[int] = None) -> list[_DocumentType]:
12921294
"""Converts the contents of this cursor to a list more efficiently than ``[doc for doc in cursor]``.
12931295

test/asynchronous/test_cursor.py

+33-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
AllowListEventListener,
3535
EventListener,
3636
OvertCommandListener,
37+
delay,
3738
ignore_deprecations,
3839
wait_until,
3940
)
@@ -44,7 +45,7 @@
4445
from pymongo.asynchronous.cursor import AsyncCursor, CursorType
4546
from pymongo.asynchronous.helpers import anext
4647
from pymongo.collation import Collation
47-
from pymongo.errors import ExecutionTimeout, InvalidOperation, OperationFailure
48+
from pymongo.errors import ExecutionTimeout, InvalidOperation, OperationFailure, PyMongoError
4849
from pymongo.operations import _IndexList
4950
from pymongo.read_concern import ReadConcern
5051
from pymongo.read_preferences import ReadPreference
@@ -1410,6 +1411,18 @@ async def test_to_list_length(self):
14101411
docs = await c.to_list(3)
14111412
self.assertEqual(len(docs), 2)
14121413

1414+
async def test_to_list_csot_applied(self):
1415+
client = await self.async_single_client(timeoutMS=500)
1416+
# Initialize the client with a larger timeout to help make test less flakey
1417+
with pymongo.timeout(2):
1418+
await client.admin.command("ping")
1419+
coll = client.pymongo.test
1420+
await coll.insert_many([{} for _ in range(5)])
1421+
cursor = coll.find({"$where": delay(1)})
1422+
with self.assertRaises(PyMongoError) as ctx:
1423+
await cursor.to_list()
1424+
self.assertTrue(ctx.exception.timeout)
1425+
14131426
@async_client_context.require_change_streams
14141427
async def test_command_cursor_to_list(self):
14151428
# Set maxAwaitTimeMS=1 to speed up the test.
@@ -1439,6 +1452,25 @@ async def test_command_cursor_to_list_length(self):
14391452
result = await db.test.aggregate([pipeline])
14401453
self.assertEqual(len(await result.to_list(1)), 1)
14411454

1455+
@async_client_context.require_failCommand_blockConnection
1456+
async def test_command_cursor_to_list_csot_applied(self):
1457+
client = await self.async_single_client(timeoutMS=500)
1458+
# Initialize the client with a larger timeout to help make test less flakey
1459+
with pymongo.timeout(2):
1460+
await client.admin.command("ping")
1461+
coll = client.pymongo.test
1462+
await coll.insert_many([{} for _ in range(5)])
1463+
fail_command = {
1464+
"configureFailPoint": "failCommand",
1465+
"mode": {"times": 5},
1466+
"data": {"failCommands": ["getMore"], "blockConnection": True, "blockTimeMS": 1000},
1467+
}
1468+
cursor = await coll.aggregate([], batchSize=1)
1469+
async with self.fail_point(fail_command):
1470+
with self.assertRaises(PyMongoError) as ctx:
1471+
await cursor.to_list()
1472+
self.assertTrue(ctx.exception.timeout)
1473+
14421474

14431475
class TestRawBatchCursor(AsyncIntegrationTest):
14441476
async def test_find_raw(self):

test/test_cursor.py

+33-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
AllowListEventListener,
3535
EventListener,
3636
OvertCommandListener,
37+
delay,
3738
ignore_deprecations,
3839
wait_until,
3940
)
@@ -42,7 +43,7 @@
4243
from bson.code import Code
4344
from pymongo import ASCENDING, DESCENDING
4445
from pymongo.collation import Collation
45-
from pymongo.errors import ExecutionTimeout, InvalidOperation, OperationFailure
46+
from pymongo.errors import ExecutionTimeout, InvalidOperation, OperationFailure, PyMongoError
4647
from pymongo.operations import _IndexList
4748
from pymongo.read_concern import ReadConcern
4849
from pymongo.read_preferences import ReadPreference
@@ -1401,6 +1402,18 @@ def test_to_list_length(self):
14011402
docs = c.to_list(3)
14021403
self.assertEqual(len(docs), 2)
14031404

1405+
def test_to_list_csot_applied(self):
1406+
client = self.single_client(timeoutMS=500)
1407+
# Initialize the client with a larger timeout to help make test less flakey
1408+
with pymongo.timeout(2):
1409+
client.admin.command("ping")
1410+
coll = client.pymongo.test
1411+
coll.insert_many([{} for _ in range(5)])
1412+
cursor = coll.find({"$where": delay(1)})
1413+
with self.assertRaises(PyMongoError) as ctx:
1414+
cursor.to_list()
1415+
self.assertTrue(ctx.exception.timeout)
1416+
14041417
@client_context.require_change_streams
14051418
def test_command_cursor_to_list(self):
14061419
# Set maxAwaitTimeMS=1 to speed up the test.
@@ -1430,6 +1443,25 @@ def test_command_cursor_to_list_length(self):
14301443
result = db.test.aggregate([pipeline])
14311444
self.assertEqual(len(result.to_list(1)), 1)
14321445

1446+
@client_context.require_failCommand_blockConnection
1447+
def test_command_cursor_to_list_csot_applied(self):
1448+
client = self.single_client(timeoutMS=500)
1449+
# Initialize the client with a larger timeout to help make test less flakey
1450+
with pymongo.timeout(2):
1451+
client.admin.command("ping")
1452+
coll = client.pymongo.test
1453+
coll.insert_many([{} for _ in range(5)])
1454+
fail_command = {
1455+
"configureFailPoint": "failCommand",
1456+
"mode": {"times": 5},
1457+
"data": {"failCommands": ["getMore"], "blockConnection": True, "blockTimeMS": 1000},
1458+
}
1459+
cursor = coll.aggregate([], batchSize=1)
1460+
with self.fail_point(fail_command):
1461+
with self.assertRaises(PyMongoError) as ctx:
1462+
cursor.to_list()
1463+
self.assertTrue(ctx.exception.timeout)
1464+
14331465

14341466
class TestRawBatchCursor(IntegrationTest):
14351467
def test_find_raw(self):

0 commit comments

Comments
 (0)