PYTHON-3288 Implement client side operation timeout (#954)

Add timeoutMS URI option and MongoClient keyword argument.
Add provisional/beta pymongo.timeout() api to set a deadline for a block of operations.
This commit is contained in:
Shane Harvey 2022-06-06 15:36:52 -04:00 committed by GitHub
parent 6b088ffa4e
commit 890cd26e1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 40245 additions and 77 deletions

View File

@ -105,6 +105,9 @@ do
crud|CRUD)
cpjson crud/tests/ crud
;;
csot|CSOT|client-side-operations-timeout)
cpjson client-side-operations-timeout/tests csot
;;
load-balancers|load_balancer)
cpjson load-balancers/tests load_balancer
;;
@ -150,6 +153,7 @@ do
;;
uri|uri-options|uri_options)
cpjson uri-options/tests uri_options
cp "$SPECS"/source/uri-options/tests/*.pem $PYMONGO/test/uri_options
;;
stable-api|versioned-api)
cpjson versioned-api/tests versioned-api

View File

@ -22,6 +22,8 @@
The maximum wire protocol version PyMongo supports.
.. autofunction:: timeout
Sub-modules:
.. toctree::

View File

@ -6,6 +6,13 @@ Changes in Version 4.2
.. warning:: PyMongo 4.2 drops support for Python 3.6: Python 3.7+ is now required.
PyMongo 4.2 brings a number of improvements including:
- Support for MongoDB 6.0.
- Provisional (beta) support for :func:`pymongo.timeout` to apply a single timeout
to an entire block of pymongo operations.
- Beta support for Queryable Encryption with MongoDB 6.0.
Bug fixes
.........

View File

@ -14,7 +14,7 @@
"""Python driver for MongoDB."""
from typing import Tuple, Union
from typing import ContextManager, Optional, Tuple, Union
ASCENDING = 1
"""Ascending sort order."""
@ -69,6 +69,7 @@ version = __version__
"""Current version of PyMongo."""
from pymongo import _csot
from pymongo.collection import ReturnDocument # noqa: F401
from pymongo.common import ( # noqa: F401
MAX_SUPPORTED_WIRE_VERSION,
@ -97,3 +98,47 @@ def has_c() -> bool:
return True
except ImportError:
return False
def timeout(seconds: Optional[float]) -> ContextManager:
"""**(Provisional)** Apply the given timeout for a block of operations.
.. note:: :func:`~pymongo.timeout` is currently provisional. Backwards
incompatible changes may occur before becoming officially supported.
Use :func:`~pymongo.timeout` in a with-statement::
with pymongo.timeout(5):
client.db.coll.insert_one({})
client.db.coll2.insert_one({})
When the with-statement is entered, a deadline is set for the entire
block. When that deadline is exceeded, any blocking pymongo operation
will raise a timeout exception. For example::
try:
with pymongo.timeout(5):
client.db.coll.insert_one({})
time.sleep(5)
# The deadline has now expired, the next operation will raise
# a timeout exception.
client.db.coll2.insert_one({})
except (ServerSelectionTimeoutError, ExecutionTimeout, WTimeoutError,
NetworkTimeout) as exc:
print(f"block timed out: {exc!r}")
:Parameters:
- `seconds`: A non-negative floating point number expressing seconds, or None.
:Raises:
- :py:class:`ValueError`: When `seconds` is negative.
.. versionadded:: 4.2
"""
if not isinstance(seconds, (int, float, type(None))):
raise TypeError("timeout must be None, an int, or a float")
if seconds and seconds < 0:
raise ValueError("timeout cannot be negative")
if seconds is not None:
seconds = float(seconds)
return _csot._TimeoutContext(seconds)

80
pymongo/_csot.py Normal file
View File

@ -0,0 +1,80 @@
# Copyright 2022-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you
# may not use this file except in compliance with the License. You
# may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
"""Internal helpers for CSOT."""
import time
from contextvars import ContextVar
from typing import Optional
TIMEOUT: ContextVar[Optional[float]] = ContextVar("TIMEOUT", default=None)
RTT: ContextVar[float] = ContextVar("RTT", default=0.0)
DEADLINE: ContextVar[float] = ContextVar("DEADLINE", default=float("inf"))
def get_timeout() -> Optional[float]:
return TIMEOUT.get(None)
def get_rtt() -> float:
return RTT.get()
def get_deadline() -> float:
return DEADLINE.get()
def set_rtt(rtt: float) -> None:
RTT.set(rtt)
def set_timeout(timeout: Optional[float]) -> None:
TIMEOUT.set(timeout)
DEADLINE.set(time.monotonic() + timeout if timeout else float("inf"))
def remaining() -> Optional[float]:
if not get_timeout():
return None
return DEADLINE.get() - time.monotonic()
def clamp_remaining(max_timeout: float) -> float:
"""Return the remaining timeout clamped to a max value."""
timeout = remaining()
if timeout is None:
return max_timeout
return min(timeout, max_timeout)
class _TimeoutContext(object):
"""Internal timeout context manager.
Use :func:`pymongo.timeout` instead::
with client.timeout(0.5):
client.test.test.insert_one({})
"""
__slots__ = ("_timeout",)
def __init__(self, timeout: Optional[float]):
self._timeout = timeout
def __enter__(self):
set_timeout(self._timeout)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
set_timeout(None)

View File

@ -330,6 +330,8 @@ class _Bulk(object):
session._apply_to(cmd, retryable, ReadPreference.PRIMARY, sock_info)
sock_info.send_cluster_time(cmd, session, client)
sock_info.add_server_api(cmd)
# CSOT: apply timeout before encoding the command.
sock_info.apply_timeout(client, cmd)
ops = islice(run.ops, run.idx_offset, None)
# Run as many ops as possible in one command.

View File

@ -14,6 +14,8 @@
"""Tools to parse mongo client options."""
from typing import Optional
from bson.codec_options import _parse_codec_options
from pymongo import common
from pymongo.auth import _build_credentials_tuple
@ -195,6 +197,7 @@ class ClientOptions(object):
self.__server_selector = options.get("server_selector", any_server_selector)
self.__auto_encryption_opts = options.get("auto_encryption_opts")
self.__load_balanced = options.get("loadbalanced")
self.__timeout = options.get("timeoutms")
@property
def _options(self):
@ -260,6 +263,14 @@ class ClientOptions(object):
"""A :class:`~pymongo.read_concern.ReadConcern` instance."""
return self.__read_concern
@property
def timeout(self) -> Optional[float]:
"""The timeout.
..versionadded: 4.2
"""
return self.__timeout
@property
def retry_writes(self):
"""If this instance should retry supported write operations."""

View File

@ -150,6 +150,7 @@ from bson.binary import Binary
from bson.int64 import Int64
from bson.son import SON
from bson.timestamp import Timestamp
from pymongo import _csot
from pymongo.cursor import _SocketManager
from pymongo.errors import (
ConfigurationError,
@ -826,7 +827,7 @@ class ClientSession:
wc = opts.write_concern
cmd = SON([(command_name, 1)])
if command_name == "commitTransaction":
if opts.max_commit_time_ms:
if opts.max_commit_time_ms and _csot.get_timeout() is None:
cmd["maxTimeMS"] = opts.max_commit_time_ms
# Transaction spec says that after the initial commit attempt,

View File

@ -116,6 +116,7 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
write_concern: Optional[WriteConcern] = None,
read_concern: Optional["ReadConcern"] = None,
session: Optional["ClientSession"] = None,
timeout: Optional[float] = None,
encrypted_fields: Optional[Mapping[str, Any]] = None,
**kwargs: Any,
) -> None:
@ -198,6 +199,7 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
read_preference or database.read_preference,
write_concern or database.write_concern,
read_concern or database.read_concern,
timeout if timeout is not None else database.timeout,
)
if not isinstance(name, str):
raise TypeError("name must be an instance of str")
@ -390,6 +392,7 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional["ReadConcern"] = None,
timeout: Optional[float] = None,
) -> "Collection[_DocumentType]":
"""Get a clone of this collection changing the specified settings.
@ -428,6 +431,7 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
read_preference or self.read_preference,
write_concern or self.write_concern,
read_concern or self.read_concern,
timeout=timeout if timeout is not None else self.timeout,
)
def bulk_write(

View File

@ -339,6 +339,15 @@ def validate_timeout_or_none_or_zero(option: Any, value: Any) -> Optional[float]
return validate_positive_float(option, value) / 1000.0
def validate_timeoutms(option: Any, value: Any) -> Optional[float]:
"""Validates a timeout specified in milliseconds returning
a value in floating point seconds.
"""
if value is None:
return None
return validate_positive_float_or_zero(option, value) / 1000.0
def validate_max_staleness(option: str, value: Any) -> int:
"""Validates maxStalenessSeconds according to the Max Staleness Spec."""
if value == -1 or value == "-1":
@ -658,6 +667,7 @@ URI_OPTIONS_VALIDATOR_MAP: Dict[str, Callable[[Any, Any], Any]] = {
"zlibcompressionlevel": validate_zlib_compression_level,
"srvservicename": validate_string,
"srvmaxhosts": validate_non_negative_integer,
"timeoutms": validate_timeoutms,
}
# Dictionary where keys are the names of URI options specific to pymongo,
@ -821,8 +831,8 @@ class BaseObject(object):
read_preference: _ServerMode,
write_concern: WriteConcern,
read_concern: ReadConcern,
timeout: Optional[float],
) -> None:
if not isinstance(codec_options, CodecOptions):
raise TypeError("codec_options must be an instance of bson.codec_options.CodecOptions")
self.__codec_options = codec_options
@ -845,6 +855,12 @@ class BaseObject(object):
raise TypeError("read_concern must be an instance of pymongo.read_concern.ReadConcern")
self.__read_concern = read_concern
if not isinstance(timeout, (int, float, type(None))):
raise TypeError("timeout must be None, an int, or a float")
if timeout and timeout < 0:
raise TypeError("timeout cannot be negative")
self.__timeout = float(timeout) if timeout else None
@property
def codec_options(self) -> CodecOptions:
"""Read only access to the :class:`~bson.codec_options.CodecOptions`
@ -894,6 +910,14 @@ class BaseObject(object):
"""
return self.__read_concern
@property
def timeout(self) -> Optional[float]:
"""Read only access to the timeout of this instance.
.. versionadded:: 4.2
"""
return self.__timeout
class _CaseInsensitiveDictionary(abc.MutableMapping):
def __init__(self, *args, **kwargs):

View File

@ -75,6 +75,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
read_preference: Optional[_ServerMode] = None,
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
timeout: Optional[float] = None,
) -> None:
"""Get a database by client and name.
@ -127,6 +128,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
read_preference or client.read_preference,
write_concern or client.write_concern,
read_concern or client.read_concern,
timeout if timeout is not None else client.timeout,
)
if not isinstance(name, str):
@ -154,6 +156,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
read_preference: Optional[_ServerMode] = None,
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
timeout: Optional[float] = None,
) -> "Database[_DocumentType]":
"""Get a clone of this database changing the specified settings.
@ -193,6 +196,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
read_preference or self.read_preference,
write_concern or self.write_concern,
read_concern or self.read_concern,
timeout if timeout is not None else self.timeout,
)
def __eq__(self, other: Any) -> bool:
@ -241,6 +245,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
read_preference: Optional[_ServerMode] = None,
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
timeout: Optional[float] = None,
) -> Collection[_DocumentType]:
"""Get a :class:`~pymongo.collection.Collection` with the given name
and options.
@ -280,7 +285,14 @@ class Database(common.BaseObject, Generic[_DocumentType]):
used.
"""
return Collection(
self, name, False, codec_options, read_preference, write_concern, read_concern
self,
name,
False,
codec_options,
read_preference,
write_concern,
read_concern,
timeout=timeout,
)
def create_collection(
@ -291,6 +303,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
session: Optional["ClientSession"] = None,
timeout: Optional[float] = None,
encrypted_fields: Optional[Mapping[str, Any]] = None,
**kwargs: Any,
) -> Collection[_DocumentType]:
@ -421,6 +434,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
write_concern,
read_concern,
session=s,
timeout=timeout,
encrypted_fields=encrypted_fields,
**kwargs,
)

View File

@ -16,6 +16,7 @@
import contextlib
import enum
import socket
import uuid
import weakref
from typing import Any, Mapping, Optional, Sequence
@ -38,6 +39,7 @@ from bson.codec_options import CodecOptions
from bson.errors import BSONError
from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument, _inflate_bson
from bson.son import SON
from pymongo import _csot
from pymongo.daemon import _spawn_daemon
from pymongo.encryption_options import AutoEncryptionOpts
from pymongo.errors import (
@ -47,6 +49,7 @@ from pymongo.errors import (
ServerSelectionTimeoutError,
)
from pymongo.mongo_client import MongoClient
from pymongo.network import BLOCKING_IO_ERRORS
from pymongo.pool import PoolOptions, _configured_socket
from pymongo.read_concern import ReadConcern
from pymongo.ssl_support import get_ssl_context
@ -119,9 +122,11 @@ class _EncryptionIO(MongoCryptCallback): # type: ignore
False, # allow_invalid_hostnames
False,
) # disable_ocsp_endpoint_check
# CSOT: set timeout for socket creation.
connect_timeout = max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0.001)
opts = PoolOptions(
connect_timeout=_KMS_CONNECT_TIMEOUT,
socket_timeout=_KMS_CONNECT_TIMEOUT,
connect_timeout=connect_timeout,
socket_timeout=connect_timeout,
ssl_context=ctx,
)
host, port = parse_host(endpoint, _HTTPS_PORT)
@ -129,10 +134,14 @@ class _EncryptionIO(MongoCryptCallback): # type: ignore
try:
conn.sendall(message)
while kms_context.bytes_needed > 0:
# CSOT: update timeout.
conn.settimeout(max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0))
data = conn.recv(kms_context.bytes_needed)
if not data:
raise OSError("KMS connection closed")
kms_context.feed(data)
except BLOCKING_IO_ERRORS:
raise socket.timeout("timed out")
finally:
conn.close()

View File

@ -300,6 +300,9 @@ class _Query(object):
self._as_command = None
self.exhaust = exhaust
def reset(self):
self._as_command = None
def namespace(self):
return "%s.%s" % (self.db, self.coll)
@ -320,7 +323,7 @@ class _Query(object):
sock_info.validate_session(self.client, self.session)
return use_find_cmd
def as_command(self, sock_info):
def as_command(self, sock_info, apply_timeout=False):
"""Return a find command document for this query."""
# We use the command twice: on the wire and for command monitoring.
# Generate it once, for speed and to avoid repeating side-effects.
@ -356,6 +359,9 @@ class _Query(object):
client = self.client
if client._encrypter and not client._encrypter._bypass_auto_encryption:
cmd = client._encrypter.encrypt(self.db, cmd, self.codec_options)
# Support CSOT
if apply_timeout:
sock_info.apply_timeout(client, cmd)
self._as_command = cmd, self.db
return self._as_command
@ -371,7 +377,7 @@ class _Query(object):
spec = self.spec
if use_cmd:
spec = self.as_command(sock_info)[0]
spec = self.as_command(sock_info, apply_timeout=True)[0]
request_id, msg, size, _ = _op_msg(
0,
spec,
@ -457,6 +463,9 @@ class _GetMore(object):
self.exhaust = exhaust
self.comment = comment
def reset(self):
self._as_command = None
def namespace(self):
return "%s.%s" % (self.db, self.coll)
@ -471,7 +480,7 @@ class _GetMore(object):
sock_info.validate_session(self.client, self.session)
return use_cmd
def as_command(self, sock_info):
def as_command(self, sock_info, apply_timeout=False):
"""Return a getMore command document for this query."""
# See _Query.as_command for an explanation of this caching.
if self._as_command is not None:
@ -493,6 +502,9 @@ class _GetMore(object):
client = self.client
if client._encrypter and not client._encrypter._bypass_auto_encryption:
cmd = client._encrypter.encrypt(self.db, cmd, self.codec_options)
# Support CSOT
if apply_timeout:
sock_info.apply_timeout(client, cmd=None)
self._as_command = cmd, self.db
return self._as_command
@ -503,7 +515,7 @@ class _GetMore(object):
ctx = sock_info.compression_context
if use_cmd:
spec = self.as_command(sock_info)[0]
spec = self.as_command(sock_info, apply_timeout=True)[0]
if self.sock_mgr:
flags = _OpMsg.EXHAUST_ALLOWED
else:

View File

@ -57,6 +57,7 @@ from bson.codec_options import DEFAULT_CODEC_OPTIONS, CodecOptions, TypeRegistry
from bson.son import SON
from bson.timestamp import Timestamp
from pymongo import (
_csot,
client_session,
common,
database,
@ -260,6 +261,10 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
replaced. Defaults to `None` (no limit).
- `maxConnecting` (optional): The maximum number of connections that
each pool can establish concurrently. Defaults to `2`.
- `timeoutMS`: (integer or None) Controls how long (in
milliseconds) the driver will wait when executing an operation
(including retry attempts) before raising a timeout error.
``0`` or ``None`` means no timeout.
- `socketTimeoutMS`: (integer or None) Controls how long (in
milliseconds) the driver will wait for a response after sending an
ordinary (non-monitoring) database operation before concluding that
@ -540,6 +545,9 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
.. seealso:: The MongoDB documentation on `connections <https://dochub.mongodb.org/core/connections>`_.
.. versionchanged:: 4.2
Added the ``timeoutMS`` keyword argument.
.. versionchanged:: 4.0
- Removed the fsync, unlock, is_locked, database_names, and
@ -780,6 +788,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
options.read_preference,
options.write_concern,
options.read_concern,
options.timeout,
)
self._topology_settings = TopologySettings(
@ -1273,6 +1282,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
)
def _cmd(session, server, sock_info, read_preference):
operation.reset() # Reset op in case of retry.
return server.run_operation(
sock_info, operation, read_preference, self._event_listeners, unpack_res
)
@ -1303,6 +1313,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
max_wire_version = 0
last_error: Optional[Exception] = None
retrying = False
multiple_retries = _csot.get_timeout() is not None
def is_retrying():
return bulk.retrying if bulk else retrying
@ -1350,7 +1361,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
retryable_error = exc.has_error_label("RetryableWriteError")
if retryable_error:
session._unpin()
if is_retrying() or not retryable_error:
if not retryable_error or (is_retrying() and not multiple_retries):
raise
if bulk:
bulk.retrying = True
@ -1371,6 +1382,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
)
last_error: Optional[Exception] = None
retrying = False
multiple_retries = _csot.get_timeout() is not None
while True:
try:
@ -1394,12 +1406,12 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
# most likely be a waste of time.
raise
except ConnectionFailure as exc:
if not retryable or retrying:
if not retryable or (retrying and not multiple_retries):
raise
retrying = True
last_error = exc
except OperationFailure as exc:
if not retryable or retrying:
if not retryable or (retrying and not multiple_retries):
raise
if exc.code not in helpers._RETRYABLE_ERROR_CODES:
raise
@ -1922,6 +1934,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional["ReadConcern"] = None,
timeout: Optional[float] = None,
) -> database.Database[_DocumentType]:
"""Get a :class:`~pymongo.database.Database` with the given name and
options.
@ -1972,7 +1985,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
name = self.__default_database_name
return database.Database(
self, name, codec_options, read_preference, write_concern, read_concern
self, name, codec_options, read_preference, write_concern, read_concern, timeout
)
def _database_default_options(self, name):

View File

@ -21,7 +21,7 @@ import struct
import time
from bson import _decode_all_selective
from pymongo import helpers, message
from pymongo import _csot, helpers, message, ssl_support
from pymongo.common import MAX_MESSAGE_SIZE
from pymongo.compression_support import _NO_COMPRESSION, decompress
from pymongo.errors import (
@ -59,6 +59,7 @@ def command(
unacknowledged=False,
user_fields=None,
exhaust_allowed=False,
write_concern=None,
):
"""Execute a command over the socket, or raise socket.error.
@ -115,6 +116,12 @@ def command(
if client and client._encrypter and not client._encrypter._bypass_auto_encryption:
spec = orig = client._encrypter.encrypt(dbname, spec, codec_options)
# Support CSOT
if client:
sock_info.apply_timeout(client, spec, write_concern)
elif write_concern and not write_concern.is_server_default:
spec["writeConcern"] = write_concern.document
if use_op_msg:
flags = _OpMsg.MORE_TO_COME if unacknowledged else 0
flags |= _OpMsg.EXHAUST_ALLOWED if exhaust_allowed else 0
@ -198,11 +205,14 @@ _UNPACK_COMPRESSION_HEADER = struct.Struct("<iiB").unpack
def receive_message(sock_info, request_id, max_message_size=MAX_MESSAGE_SIZE):
"""Receive a raw BSON message or raise socket.error."""
timeout = sock_info.sock.gettimeout()
if timeout:
deadline = time.monotonic() + timeout
if _csot.get_timeout():
deadline = _csot.get_deadline()
else:
deadline = None
timeout = sock_info.sock.gettimeout()
if timeout:
deadline = time.monotonic() + timeout
else:
deadline = None
# Ignore the response's request id.
length, _, response_to, op_code = _UNPACK_HEADER(
_receive_data_on_socket(sock_info, 16, deadline)
@ -271,6 +281,10 @@ def wait_for_read(sock_info, deadline):
raise socket.timeout("timed out")
# Errors raised by sockets (and TLS sockets) when in non-blocking mode.
BLOCKING_IO_ERRORS = (BlockingIOError,) + ssl_support.BLOCKING_IO_ERRORS
def _receive_data_on_socket(sock_info, length, deadline):
buf = bytearray(length)
mv = memoryview(buf)
@ -278,7 +292,14 @@ def _receive_data_on_socket(sock_info, length, deadline):
while bytes_read < length:
try:
wait_for_read(sock_info, deadline)
# CSOT: Update timeout. When the timeout has expired perform one
# final non-blocking recv. This helps avoid spurious timeouts when
# the response is actually already buffered on the client.
if _csot.get_timeout():
sock_info.set_socket_timeout(max(deadline - time.monotonic(), 0))
chunk_length = sock_info.sock.recv_into(mv[bytes_read:])
except BLOCKING_IO_ERRORS:
raise socket.timeout("timed out")
except (IOError, OSError) as exc: # noqa: B014
if _errno_from_exception(exc) == errno.EINTR:
continue

View File

@ -48,6 +48,8 @@ from cryptography.x509.oid import ExtendedKeyUsageOID as _ExtendedKeyUsageOID
from requests import post as _post
from requests.exceptions import RequestException as _RequestException
from pymongo import _csot
# Note: the functions in this module generally return 1 or 0. The reason
# is simple. The entry point, ocsp_callback, is registered as a callback
# with OpenSSL through PyOpenSSL. The callback must return 1 (success) or
@ -235,12 +237,16 @@ def _get_ocsp_response(cert, issuer, uri, ocsp_response_cache):
ocsp_response = ocsp_response_cache[ocsp_request]
_LOGGER.debug("Using cached OCSP response.")
except KeyError:
# CSOT: use the configured timeout or 5 seconds, whichever is smaller.
# Note that request's timeout works differently and does not imply an absolute
# deadline: https://requests.readthedocs.io/en/stable/user/quickstart/#timeouts
timeout = max(_csot.clamp_remaining(5), 0.001)
try:
response = _post(
uri,
data=ocsp_request.public_bytes(_Encoding.DER),
headers={"Content-Type": "application/ocsp-request"},
timeout=5,
timeout=timeout,
)
except _RequestException as exc:
_LOGGER.debug("HTTP request failed: %s", exc)

View File

@ -27,7 +27,7 @@ from typing import Any, NoReturn, Optional
from bson import DEFAULT_CODEC_OPTIONS
from bson.son import SON
from pymongo import __version__, auth, helpers
from pymongo import __version__, _csot, auth, helpers
from pymongo.client_session import _validate_session_write_concern
from pymongo.common import (
MAX_BSON_SIZE,
@ -46,6 +46,7 @@ from pymongo.errors import (
ConfigurationError,
ConnectionFailure,
DocumentTooLarge,
ExecutionTimeout,
InvalidOperation,
NetworkTimeout,
NotPrimaryError,
@ -557,6 +558,43 @@ class SocketInfo(object):
self.pinned_txn = False
self.pinned_cursor = False
self.active = False
self.last_timeout = self.opts.socket_timeout
def set_socket_timeout(self, timeout):
"""Cache last timeout to avoid duplicate calls to sock.settimeout."""
if timeout == self.last_timeout:
return
self.last_timeout = timeout
self.sock.settimeout(timeout)
def apply_timeout(self, client, cmd, write_concern=None):
# CSOT: use remaining timeout when set.
timeout = _csot.remaining()
if timeout is None:
# Reset the socket timeout unless we're performing a streaming monitor check.
if not self.more_to_come:
self.set_socket_timeout(self.opts.socket_timeout)
if cmd and write_concern and not write_concern.is_server_default:
cmd["writeConcern"] = write_concern.document
return None
# RTT validation.
rtt = _csot.get_rtt()
max_time_ms = timeout - rtt
if max_time_ms < 0:
# CSOT: raise an error without running the command since we know it will time out.
errmsg = f"operation would exceed time limit, remaining timeout:{timeout:.5f} <= network round trip time:{rtt:.5f}"
raise ExecutionTimeout(
errmsg, 50, {"ok": 0, "errmsg": errmsg, "code": 50}, self.max_wire_version
)
if cmd is not None:
cmd["maxTimeMS"] = int(max_time_ms * 1000)
wc = write_concern.document if write_concern else {}
wc.pop("wtimeout", None)
if wc:
cmd["writeConcern"] = wc
self.set_socket_timeout(timeout)
return timeout
def pin_txn(self):
self.pinned_txn = True
@ -602,7 +640,7 @@ class SocketInfo(object):
awaitable = True
# If connect_timeout is None there is no timeout.
if self.opts.connect_timeout:
self.sock.settimeout(self.opts.connect_timeout + heartbeat_frequency)
self.set_socket_timeout(self.opts.connect_timeout + heartbeat_frequency)
if not performing_handshake and cluster_time is not None:
cmd["$clusterTime"] = cluster_time
@ -714,8 +752,6 @@ class SocketInfo(object):
if not (write_concern is None or write_concern.acknowledged or collation is None):
raise ConfigurationError("Collation is unsupported for unacknowledged writes.")
if write_concern and not write_concern.is_server_default:
spec["writeConcern"] = write_concern.document
self.add_server_api(spec)
if session:
@ -748,6 +784,7 @@ class SocketInfo(object):
unacknowledged=unacknowledged,
user_fields=user_fields,
exhaust_allowed=exhaust_allowed,
write_concern=write_concern,
)
except (OperationFailure, NotPrimaryError):
raise
@ -978,7 +1015,13 @@ def _create_connection(address, options):
_set_non_inheritable_non_atomic(sock.fileno())
try:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.settimeout(options.connect_timeout)
# CSOT: apply timeout to socket connect.
timeout = _csot.remaining()
if timeout is None:
timeout = options.connect_timeout
elif timeout <= 0:
raise socket.timeout("timed out")
sock.settimeout(timeout)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, True)
_set_keepalive_times(sock)
sock.connect(sa)
@ -1416,7 +1459,9 @@ class Pool:
self.operation_count += 1
# Get a free socket or create one.
if self.opts.wait_queue_timeout:
if _csot.get_timeout():
deadline = _csot.get_deadline()
elif self.opts.wait_queue_timeout:
deadline = time.monotonic() + self.opts.wait_queue_timeout
else:
deadline = None
@ -1582,25 +1627,25 @@ class Pool:
listeners.publish_connection_check_out_failed(
self.address, ConnectionCheckOutFailedReason.TIMEOUT
)
timeout = _csot.get_timeout() or self.opts.wait_queue_timeout
if self.opts.load_balanced:
other_ops = self.active_sockets - self.ncursors - self.ntxns
raise ConnectionFailure(
"Timeout waiting for connection from the connection pool. "
"maxPoolSize: %s, connections in use by cursors: %s, "
"connections in use by transactions: %s, connections in use "
"by other operations: %s, wait_queue_timeout: %s"
"by other operations: %s, timeout: %s"
% (
self.opts.max_pool_size,
self.ncursors,
self.ntxns,
other_ops,
self.opts.wait_queue_timeout,
timeout,
)
)
raise ConnectionFailure(
"Timed out while checking out a connection from connection pool. "
"maxPoolSize: %s, wait_queue_timeout: %s"
% (self.opts.max_pool_size, self.opts.wait_queue_timeout)
"maxPoolSize: %s, timeout: %s" % (self.opts.max_pool_size, timeout)
)
def __del__(self):

View File

@ -82,7 +82,7 @@ def _is_ip_address(address):
# According to the docs for Connection.send it can raise
# WantX509LookupError and should be retried.
_RETRY_ERRORS = (_SSL.WantReadError, _SSL.WantWriteError, _SSL.WantX509LookupError)
BLOCKING_IO_ERRORS = (_SSL.WantReadError, _SSL.WantWriteError, _SSL.WantX509LookupError)
def _ragged_eof(exc):
@ -106,7 +106,7 @@ class _sslConn(_SSL.Connection):
while True:
try:
return call(*args, **kwargs)
except _RETRY_ERRORS as exc:
except BLOCKING_IO_ERRORS as exc:
if isinstance(exc, _SSL.WantReadError):
want_read = True
want_write = False

View File

@ -27,6 +27,9 @@ OP_NO_RENEGOTIATION = getattr(_ssl, "OP_NO_RENEGOTIATION", 0)
HAS_SNI = getattr(_ssl, "HAS_SNI", False)
IS_PYOPENSSL = False
# Errors raised by SSL sockets when in non-blocking mode.
BLOCKING_IO_ERRORS = (_ssl.SSLWantReadError, _ssl.SSLWantWriteError)
# Base Exception class
SSLError = _ssl.SSLError

View File

@ -38,6 +38,7 @@ if HAVE_SSL:
HAS_SNI = _ssl.HAS_SNI
IPADDR_SAFE = True
SSLError = _ssl.SSLError
BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS
def get_ssl_context(
certfile,
@ -91,6 +92,7 @@ else:
HAS_SNI = False
IPADDR_SAFE = False
BLOCKING_IO_ERRORS = () # type: ignore
def get_ssl_context(*dummy): # type: ignore
"""No ssl module, raise ConfigurationError."""

View File

@ -23,7 +23,7 @@ import warnings
import weakref
from typing import Any
from pymongo import common, helpers, periodic_executor
from pymongo import _csot, common, helpers, periodic_executor
from pymongo.client_session import _ServerSessionPool
from pymongo.errors import (
ConfigurationError,
@ -191,6 +191,13 @@ class Topology(object):
with self._lock:
self._ensure_opened()
def get_server_selection_timeout(self):
# CSOT: use remaining timeout when set.
timeout = _csot.remaining()
if timeout is None:
return self._settings.server_selection_timeout
return timeout
def select_servers(self, selector, server_selection_timeout=None, address=None):
"""Return a list of Servers matching selector, or time out.
@ -208,7 +215,7 @@ class Topology(object):
`server_selection_timeout` if no matching servers are found.
"""
if server_selection_timeout is None:
server_timeout = self._settings.server_selection_timeout
server_timeout = self.get_server_selection_timeout()
else:
server_timeout = server_selection_timeout
@ -250,8 +257,7 @@ class Topology(object):
self._description.check_compatible()
return server_descriptions
def select_server(self, selector, server_selection_timeout=None, address=None):
"""Like select_servers, but choose a random server if several match."""
def _select_server(self, selector, server_selection_timeout=None, address=None):
servers = self.select_servers(selector, server_selection_timeout, address)
if len(servers) == 1:
return servers[0]
@ -261,6 +267,12 @@ class Topology(object):
else:
return server2
def select_server(self, selector, server_selection_timeout=None, address=None):
"""Like select_servers, but choose a random server if several match."""
server = self._select_server(selector, server_selection_timeout, address)
_csot.set_rtt(server.description.round_trip_time)
return server
def select_server_by_address(self, address, server_selection_timeout=None):
"""Return a Server for "address", reconnecting if necessary.
@ -535,11 +547,11 @@ class Topology(object):
if self._description.topology_type == TOPOLOGY_TYPE.Single:
if not self._description.has_known_servers:
self._select_servers_loop(
any_server_selector, self._settings.server_selection_timeout, None
any_server_selector, self.get_server_selection_timeout(), None
)
elif not self._description.readable_servers:
self._select_servers_loop(
readable_server_selector, self._settings.server_selection_timeout, None
readable_server_selector, self.get_server_selection_timeout(), None
)
session_timeout = self._description.logical_session_timeout_minutes

159
test/csot/bulkWrite.json Normal file
View File

@ -0,0 +1,159 @@
{
"description": "timeoutMS behaves correctly for bulkWrite operations",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "timeoutMS applied to entire bulkWrite, not individual commands",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"insert",
"update"
],
"blockConnection": true,
"blockTimeMS": 120
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {
"_id": 1
}
}
},
{
"name": "bulkWrite",
"object": "collection",
"arguments": {
"requests": [
{
"insertOne": {
"document": {
"_id": 1
}
}
},
{
"replaceOne": {
"filter": {
"_id": 1
},
"replacement": {
"x": 1
}
}
}
],
"timeoutMS": 200
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll"
}
}
},
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "update",
"databaseName": "test",
"command": {
"update": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,598 @@
{
"description": "timeoutMS behaves correctly for change streams",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4",
"topologies": [
"replicaset",
"sharded-replicaset"
]
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
],
"ignoreCommandMonitoringEvents": [
"killCursors"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "error if maxAwaitTimeMS is greater than timeoutMS",
"operations": [
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMS": 5,
"maxAwaitTimeMS": 10
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "error if maxAwaitTimeMS is equal to timeoutMS",
"operations": [
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMS": 5,
"maxAwaitTimeMS": 5
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "timeoutMS applied to initial aggregate",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"aggregate"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMS": 50
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore if maxAwaitTimeMS is not set",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"aggregate",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 30
}
}
}
},
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMS": 1050
},
"saveResultAsEntity": "changeStream"
},
{
"name": "iterateOnce",
"object": "changeStream"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore if maxAwaitTimeMS is set",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"aggregate",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMS": 20,
"batchSize": 2,
"maxAwaitTimeMS": 1
},
"saveResultAsEntity": "changeStream"
},
{
"name": "iterateOnce",
"object": "changeStream"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": 1
}
}
}
]
}
]
},
{
"description": "timeoutMS applies to full resume attempt in a next call",
"operations": [
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMS": 20
},
"saveResultAsEntity": "changeStream"
},
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"getMore",
"aggregate"
],
"blockConnection": true,
"blockTimeMS": 12,
"errorCode": 7,
"errorLabels": [
"ResumableChangeStreamError"
]
}
}
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "changeStream",
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
},
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "change stream can be iterated again if previous iteration times out",
"operations": [
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"maxAwaitTimeMS": 1,
"timeoutMS": 100
},
"saveResultAsEntity": "changeStream"
},
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"getMore"
],
"blockConnection": true,
"blockTimeMS": 150
}
}
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "changeStream",
"expectError": {
"isTimeoutError": true
}
},
{
"name": "iterateOnce",
"object": "changeStream"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll"
}
}
},
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll"
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore - failure",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "createChangeStream",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMS": 10
},
"saveResultAsEntity": "changeStream"
},
{
"name": "iterateUntilDocumentOrError",
"object": "changeStream",
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "aggregate",
"databaseName": "test",
"command": {
"aggregate": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll"
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,239 @@
{
"description": "timeoutMS behaves correctly when closing cursors",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent",
"commandSucceededEvent",
"commandFailedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": [
{
"_id": 0
},
{
"_id": 1
},
{
"_id": 2
}
]
}
],
"tests": [
{
"description": "timeoutMS is refreshed for close",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"getMore"
],
"blockConnection": true,
"blockTimeMS": 50
}
}
}
},
{
"name": "createFindCursor",
"object": "collection",
"arguments": {
"filter": {},
"batchSize": 2,
"timeoutMS": 20
},
"saveResultAsEntity": "cursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "cursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "cursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "cursor",
"expectError": {
"isTimeoutError": true
}
},
{
"name": "close",
"object": "cursor"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find"
}
},
{
"commandSucceededEvent": {
"commandName": "find"
}
},
{
"commandStartedEvent": {
"commandName": "getMore"
}
},
{
"commandFailedEvent": {
"commandName": "getMore"
}
},
{
"commandStartedEvent": {
"command": {
"killCursors": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
},
"commandName": "killCursors"
}
},
{
"commandSucceededEvent": {
"commandName": "killCursors"
}
}
]
}
]
},
{
"description": "timeoutMS can be overridden for close",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "client",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"killCursors"
],
"blockConnection": true,
"blockTimeMS": 30
}
}
}
},
{
"name": "createFindCursor",
"object": "collection",
"arguments": {
"filter": {},
"batchSize": 2,
"timeoutMS": 20
},
"saveResultAsEntity": "cursor"
},
{
"name": "close",
"object": "cursor",
"arguments": {
"timeoutMS": 40
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find"
}
},
{
"commandSucceededEvent": {
"commandName": "find"
}
},
{
"commandStartedEvent": {
"command": {
"killCursors": "collection",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
},
"commandName": "killCursors"
}
},
{
"commandSucceededEvent": {
"commandName": "killCursors"
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,260 @@
{
"description": "timeoutMS behaves correctly during command execution",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.9"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
},
{
"collectionName": "timeoutColl",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "maxTimeMS value in the command is less than timeoutMS",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": "alwaysOn",
"data": {
"failCommands": [
"hello",
"isMaster"
],
"appName": "reduceMaxTimeMSTest",
"blockConnection": true,
"blockTimeMS": 20
}
}
}
},
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"uriOptions": {
"appName": "reduceMaxTimeMSTest",
"w": 1
},
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "regularCollection",
"database": "database",
"collectionName": "coll"
}
},
{
"collection": {
"id": "timeoutCollection",
"database": "database",
"collectionName": "timeoutColl",
"collectionOptions": {
"timeoutMS": 60
}
}
}
]
}
},
{
"name": "insertOne",
"object": "regularCollection",
"arguments": {
"document": {
"_id": 1
}
}
},
{
"name": "insertOne",
"object": "timeoutCollection",
"arguments": {
"document": {
"_id": 2
}
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
},
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "timeoutColl",
"maxTimeMS": {
"$$lte": 60
}
}
}
}
]
}
]
},
{
"description": "command is not sent if RTT is greater than timeoutMS",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": "alwaysOn",
"data": {
"failCommands": [
"hello",
"isMaster"
],
"appName": "rttTooHighTest",
"blockConnection": true,
"blockTimeMS": 20
}
}
}
},
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"uriOptions": {
"appName": "rttTooHighTest",
"w": 1
},
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "regularCollection",
"database": "database",
"collectionName": "coll"
}
},
{
"collection": {
"id": "timeoutCollection",
"database": "database",
"collectionName": "timeoutColl",
"collectionOptions": {
"timeoutMS": 2
}
}
}
]
}
},
{
"name": "insertOne",
"object": "regularCollection",
"arguments": {
"document": {
"_id": 1
}
}
},
{
"name": "insertOne",
"object": "timeoutCollection",
"arguments": {
"document": {
"_id": 2
}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,191 @@
{
"description": "timeoutMS behaves correctly for the withTransaction API",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4",
"topologies": [
"replicaset",
"sharded-replicaset"
]
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 50
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
},
{
"session": {
"id": "session",
"client": "client"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "withTransaction raises a client-side error if timeoutMS is overridden inside the callback",
"operations": [
{
"name": "withTransaction",
"object": "session",
"arguments": {
"callback": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"_id": 1
},
"session": "session",
"timeoutMS": 100
},
"expectError": {
"isClientError": true
}
}
]
}
}
],
"expectEvents": [
{
"client": "client",
"events": []
}
]
},
{
"description": "timeoutMS is not refreshed for each operation in the callback",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"insert"
],
"blockConnection": true,
"blockTimeMS": 30
}
}
}
},
{
"name": "withTransaction",
"object": "session",
"arguments": {
"callback": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"_id": 1
},
"session": "session"
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"_id": 2
},
"session": "session"
},
"expectError": {
"isTimeoutError": true
}
}
]
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
}
]
}

113
test/csot/cursors.json Normal file
View File

@ -0,0 +1,113 @@
{
"description": "tests for timeoutMS behavior that applies to all cursor types",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client"
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "find errors if timeoutMode is set and timeoutMS is not",
"operations": [
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "cursorLifetime"
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "collection aggregate errors if timeoutMode is set and timeoutMS is not",
"operations": [
{
"name": "aggregate",
"object": "collection",
"arguments": {
"pipeline": [],
"timeoutMode": "cursorLifetime"
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "database aggregate errors if timeoutMode is set and timeoutMS is not",
"operations": [
{
"name": "aggregate",
"object": "database",
"arguments": {
"pipeline": [],
"timeoutMode": "cursorLifetime"
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "listCollections errors if timeoutMode is set and timeoutMS is not",
"operations": [
{
"name": "listCollections",
"object": "database",
"arguments": {
"filter": {},
"timeoutMode": "cursorLifetime"
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "listIndexes errors if timeoutMode is set and timeoutMS is not",
"operations": [
{
"name": "listIndexes",
"object": "collection",
"arguments": {
"timeoutMode": "cursorLifetime"
},
"expectError": {
"isClientError": true
}
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,181 @@
{
"description": "MaxTimeMSExpired server errors are transformed into a custom timeout error",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.0",
"topologies": [
"replicaset"
]
},
{
"minServerVersion": "4.2",
"topologies": [
"replicaset",
"sharded"
]
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 250
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "basic MaxTimeMSExpired error is transformed",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"insert"
],
"errorCode": 50
}
}
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"_id": 1
}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "write concern error MaxTimeMSExpired is transformed",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"insert"
],
"writeConcernError": {
"code": 50,
"errmsg": "maxTimeMS expired"
}
}
}
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"_id": 1
}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,370 @@
{
"description": "timeoutMS behaves correctly for advanced GridFS API operations",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 50
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"bucket": {
"id": "bucket",
"database": "database"
}
},
{
"collection": {
"id": "filesCollection",
"database": "database",
"collectionName": "fs.files"
}
},
{
"collection": {
"id": "chunksCollection",
"database": "database",
"collectionName": "fs.chunks"
}
}
],
"initialData": [
{
"collectionName": "fs.files",
"databaseName": "test",
"documents": [
{
"_id": {
"$oid": "000000000000000000000005"
},
"length": 10,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"md5": "57d83cd477bfb1ccd975ab33d827a92b",
"filename": "length-10",
"contentType": "application/octet-stream",
"aliases": [],
"metadata": {}
}
]
},
{
"collectionName": "fs.chunks",
"databaseName": "test",
"documents": [
{
"_id": {
"$oid": "000000000000000000000005"
},
"files_id": {
"$oid": "000000000000000000000005"
},
"n": 0,
"data": {
"$binary": {
"base64": "ESIzRA==",
"subType": "00"
}
}
}
]
}
],
"tests": [
{
"description": "timeoutMS can be overridden for a rename",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"update"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "rename",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
},
"newFilename": "foo",
"timeoutMS": 100
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "update",
"databaseName": "test",
"command": {
"update": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS applied to update during a rename",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"update"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "rename",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
},
"newFilename": "foo"
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "update",
"databaseName": "test",
"command": {
"update": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS can be overridden for drop",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"drop"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "drop",
"object": "bucket",
"arguments": {
"timeoutMS": 100
}
}
]
},
{
"description": "timeoutMS applied to files collection drop",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"drop"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "drop",
"object": "bucket",
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "drop",
"databaseName": "test",
"command": {
"drop": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS applied to chunks collection drop",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"skip": 1
},
"data": {
"failCommands": [
"drop"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "drop",
"object": "bucket",
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to drop as a whole, not individual parts",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"drop"
],
"blockConnection": true,
"blockTimeMS": 30
}
}
}
},
{
"name": "drop",
"object": "bucket",
"expectError": {
"isTimeoutError": true
}
}
]
}
]
}

View File

@ -0,0 +1,270 @@
{
"description": "timeoutMS behaves correctly for GridFS delete operations",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 50
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"bucket": {
"id": "bucket",
"database": "database"
}
},
{
"collection": {
"id": "filesCollection",
"database": "database",
"collectionName": "fs.files"
}
},
{
"collection": {
"id": "chunksCollection",
"database": "database",
"collectionName": "fs.chunks"
}
}
],
"initialData": [
{
"collectionName": "fs.files",
"databaseName": "test",
"documents": [
{
"_id": {
"$oid": "000000000000000000000005"
},
"length": 10,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"md5": "57d83cd477bfb1ccd975ab33d827a92b",
"filename": "length-10",
"contentType": "application/octet-stream",
"aliases": [],
"metadata": {}
}
]
},
{
"collectionName": "fs.chunks",
"databaseName": "test",
"documents": [
{
"_id": {
"$oid": "000000000000000000000005"
},
"files_id": {
"$oid": "000000000000000000000005"
},
"n": 0,
"data": {
"$binary": {
"base64": "ESIzRA==",
"subType": "00"
}
}
}
]
}
],
"tests": [
{
"description": "timeoutMS can be overridden for delete",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"delete"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "delete",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
},
"timeoutMS": 100
}
}
]
},
{
"description": "timeoutMS applied to delete against the files collection",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"delete"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "delete",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "delete",
"databaseName": "test",
"command": {
"delete": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS applied to delete against the chunks collection",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"skip": 1
},
"data": {
"failCommands": [
"delete"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "delete",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to entire delete, not individual parts",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"delete"
],
"blockConnection": true,
"blockTimeMS": 30
}
}
}
},
{
"name": "delete",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
}
]
}

View File

@ -0,0 +1,344 @@
{
"description": "timeoutMS behaves correctly for GridFS download operations",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 50
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"bucket": {
"id": "bucket",
"database": "database"
}
},
{
"collection": {
"id": "filesCollection",
"database": "database",
"collectionName": "fs.files"
}
},
{
"collection": {
"id": "chunksCollection",
"database": "database",
"collectionName": "fs.chunks"
}
}
],
"initialData": [
{
"collectionName": "fs.files",
"databaseName": "test",
"documents": [
{
"_id": {
"$oid": "000000000000000000000005"
},
"length": 10,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"md5": "57d83cd477bfb1ccd975ab33d827a92b",
"filename": "length-10",
"contentType": "application/octet-stream",
"aliases": [],
"metadata": {}
}
]
},
{
"collectionName": "fs.chunks",
"databaseName": "test",
"documents": [
{
"_id": {
"$oid": "000000000000000000000005"
},
"files_id": {
"$oid": "000000000000000000000005"
},
"n": 0,
"data": {
"$binary": {
"base64": "ESIzRA==",
"subType": "00"
}
}
}
]
}
],
"tests": [
{
"description": "timeoutMS can be overridden for download",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "download",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
},
"timeoutMS": 100
}
}
]
},
{
"description": "timeoutMS applied to find to get files document",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "download",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS applied to find to get chunks",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"skip": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "download",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "fs.chunks",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS applied to entire download, not individual parts",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 30
}
}
}
},
{
"name": "download",
"object": "bucket",
"arguments": {
"id": {
"$oid": "000000000000000000000005"
}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "fs.chunks",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
}
]
}

182
test/csot/gridfs-find.json Normal file
View File

@ -0,0 +1,182 @@
{
"description": "timeoutMS behaves correctly for GridFS find operations",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 50
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"bucket": {
"id": "bucket",
"database": "database"
}
},
{
"collection": {
"id": "filesCollection",
"database": "database",
"collectionName": "fs.files"
}
},
{
"collection": {
"id": "chunksCollection",
"database": "database",
"collectionName": "fs.chunks"
}
}
],
"initialData": [
{
"collectionName": "fs.files",
"databaseName": "test",
"documents": []
},
{
"collectionName": "fs.chunks",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "timeoutMS can be overridden for a find",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "find",
"object": "bucket",
"arguments": {
"filter": {},
"timeoutMS": 100
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "timeoutMS applied to find command",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "find",
"object": "bucket",
"arguments": {
"filter": {}
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "fs.files",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,408 @@
{
"description": "timeoutMS behaves correctly for GridFS upload operations",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 50
},
"useMultipleMongoses": false
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"bucket": {
"id": "bucket",
"database": "database"
}
},
{
"collection": {
"id": "filesCollection",
"database": "database",
"collectionName": "fs.files"
}
},
{
"collection": {
"id": "chunksCollection",
"database": "database",
"collectionName": "fs.chunks"
}
}
],
"initialData": [
{
"collectionName": "fs.files",
"databaseName": "test",
"documents": []
},
{
"collectionName": "fs.chunks",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "timeoutMS can be overridden for upload",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
},
"timeoutMS": 1000
}
}
]
},
{
"description": "timeoutMS applied to initial find on files collection",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to listIndexes on files collection",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"listIndexes"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to index creation for files collection",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"createIndexes"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to listIndexes on chunks collection",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"skip": 1
},
"data": {
"failCommands": [
"listIndexes"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to index creation for chunks collection",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"skip": 1
},
"data": {
"failCommands": [
"createIndexes"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to chunk insertion",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"insert"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to creation of files document",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"skip": 1
},
"data": {
"failCommands": [
"insert"
],
"blockConnection": true,
"blockTimeMS": 55
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
{
"description": "timeoutMS applied to upload as a whole, not individual parts",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find",
"listIndexes"
],
"blockConnection": true,
"blockTimeMS": 30
}
}
}
},
{
"name": "upload",
"object": "bucket",
"arguments": {
"filename": "filename",
"source": {
"$$hexBytes": "1122334455"
}
},
"expectError": {
"isTimeoutError": true
}
}
]
}
]
}

View File

@ -0,0 +1,379 @@
{
"description": "legacy timeouts continue to work if timeoutMS is not set",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "socketTimeoutMS is not used to derive a maxTimeMS command field",
"operations": [
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client",
"observeEvents": [
"commandStartedEvent"
],
"uriOptions": {
"socketTimeoutMS": 50000
}
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
]
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"x": 1
}
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "waitQueueTimeoutMS is not used to derive a maxTimeMS command field",
"operations": [
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client",
"observeEvents": [
"commandStartedEvent"
],
"uriOptions": {
"waitQueueTimeoutMS": 50000
}
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
]
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"x": 1
}
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "wTimeoutMS is not used to derive a maxTimeMS command field",
"operations": [
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client",
"observeEvents": [
"commandStartedEvent"
],
"uriOptions": {
"wTimeoutMS": 50000
}
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
]
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"x": 1
}
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$exists": false
},
"writeConcern": {
"wtimeout": 50000
}
}
}
}
]
}
]
},
{
"description": "maxTimeMS option is used directly as the maxTimeMS field on a command",
"operations": [
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
]
}
},
{
"name": "estimatedDocumentCount",
"object": "collection",
"arguments": {
"maxTimeMS": 50000
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "count",
"databaseName": "test",
"command": {
"count": "coll",
"maxTimeMS": 50000
}
}
}
]
}
]
},
{
"description": "maxCommitTimeMS option is used directly as the maxTimeMS field on a commitTransaction command",
"runOnRequirements": [
{
"topologies": [
"replicaset",
"sharded-replicaset"
]
}
],
"operations": [
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
},
{
"session": {
"id": "session",
"client": "client",
"sessionOptions": {
"defaultTransactionOptions": {
"maxCommitTimeMS": 1000
}
}
}
}
]
}
},
{
"name": "startTransaction",
"object": "session"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"_id": 1
},
"session": "session"
}
},
{
"name": "commitTransaction",
"object": "session"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
},
{
"commandStartedEvent": {
"commandName": "commitTransaction",
"databaseName": "admin",
"command": {
"commitTransaction": 1,
"maxTimeMS": 1000
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,541 @@
{
"description": "timeoutMS behaves correctly for non-tailable cursors",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 10
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
],
"ignoreCommandMonitoringEvents": [
"killCursors"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": [
{
"_id": 0
},
{
"_id": 1
},
{
"_id": 2
}
]
},
{
"collectionName": "aggregateOutputColl",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "timeoutMS applied to find if timeoutMode is cursor_lifetime",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "cursorLifetime"
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
}
]
}
]
},
{
"description": "remaining timeoutMS applied to getMore if timeoutMode is unset",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMS": 20,
"batchSize": 2
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "remaining timeoutMS applied to getMore if timeoutMode is cursor_lifetime",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "cursorLifetime",
"timeoutMS": 20,
"batchSize": 2
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "timeoutMS applied to find if timeoutMode is iteration",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "iteration"
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore if timeoutMode is iteration - success",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "iteration",
"timeoutMS": 20,
"batchSize": 2
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore if timeoutMode is iteration - failure",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "iteration",
"batchSize": 2
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "aggregate with $out errors if timeoutMode is iteration",
"operations": [
{
"name": "aggregate",
"object": "collection",
"arguments": {
"pipeline": [
{
"$out": "aggregateOutputColl"
}
],
"timeoutMS": 100,
"timeoutMode": "iteration"
},
"expectError": {
"isClientError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": []
}
]
},
{
"description": "aggregate with $merge errors if timeoutMode is iteration",
"operations": [
{
"name": "aggregate",
"object": "collection",
"arguments": {
"pipeline": [
{
"$merge": "aggregateOutputColl"
}
],
"timeoutMS": 100,
"timeoutMode": "iteration"
},
"expectError": {
"isClientError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,311 @@
{
"description": "sessions inherit timeoutMS from their parent MongoClient",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4",
"topologies": [
"replicaset",
"sharded-replicaset"
]
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 50
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent",
"commandSucceededEvent",
"commandFailedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
},
{
"session": {
"id": "session",
"client": "client"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "timeoutMS applied to commitTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"commitTransaction"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "startTransaction",
"object": "session"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
}
},
{
"name": "commitTransaction",
"object": "session",
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll"
}
}
},
{
"commandSucceededEvent": {
"commandName": "insert"
}
},
{
"commandStartedEvent": {
"commandName": "commitTransaction",
"databaseName": "admin",
"command": {
"commitTransaction": 1,
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "commitTransaction"
}
}
]
}
]
},
{
"description": "timeoutMS applied to abortTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"abortTransaction"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "startTransaction",
"object": "session"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
}
},
{
"name": "abortTransaction",
"object": "session"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll"
}
}
},
{
"commandSucceededEvent": {
"commandName": "insert"
}
},
{
"commandStartedEvent": {
"commandName": "abortTransaction",
"databaseName": "admin",
"command": {
"abortTransaction": 1,
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "abortTransaction"
}
}
]
}
]
},
{
"description": "timeoutMS applied to withTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"insert"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "withTransaction",
"object": "session",
"arguments": {
"callback": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "insert"
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,315 @@
{
"description": "timeoutMS can be overridden for individual session operations",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4",
"topologies": [
"replicaset",
"sharded-replicaset"
]
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent",
"commandSucceededEvent",
"commandFailedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
},
{
"session": {
"id": "session",
"client": "client"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "timeoutMS can be overridden for commitTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"commitTransaction"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "startTransaction",
"object": "session"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
}
},
{
"name": "commitTransaction",
"object": "session",
"arguments": {
"timeoutMS": 50
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll"
}
}
},
{
"commandSucceededEvent": {
"commandName": "insert"
}
},
{
"commandStartedEvent": {
"commandName": "commitTransaction",
"databaseName": "admin",
"command": {
"commitTransaction": 1,
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "commitTransaction"
}
}
]
}
]
},
{
"description": "timeoutMS applied to abortTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"abortTransaction"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "startTransaction",
"object": "session"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
}
},
{
"name": "abortTransaction",
"object": "session",
"arguments": {
"timeoutMS": 50
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll"
}
}
},
{
"commandSucceededEvent": {
"commandName": "insert"
}
},
{
"commandStartedEvent": {
"commandName": "abortTransaction",
"databaseName": "admin",
"command": {
"abortTransaction": 1,
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "abortTransaction"
}
}
]
}
]
},
{
"description": "timeoutMS applied to withTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"insert"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "withTransaction",
"object": "session",
"arguments": {
"timeoutMS": 50,
"callback": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "insert"
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,311 @@
{
"description": "timeoutMS can be overridden at the level of a ClientSession",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4",
"topologies": [
"replicaset",
"sharded-replicaset"
]
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent",
"commandSucceededEvent",
"commandFailedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
},
{
"session": {
"id": "session",
"client": "client",
"sessionOptions": {
"defaultTimeoutMS": 50
}
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"documents": []
}
],
"tests": [
{
"description": "timeoutMS applied to commitTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"commitTransaction"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "startTransaction",
"object": "session"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
}
},
{
"name": "commitTransaction",
"object": "session",
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll"
}
}
},
{
"commandSucceededEvent": {
"commandName": "insert"
}
},
{
"commandStartedEvent": {
"commandName": "commitTransaction",
"databaseName": "admin",
"command": {
"commitTransaction": 1,
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "commitTransaction"
}
}
]
}
]
},
{
"description": "timeoutMS applied to abortTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"abortTransaction"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "startTransaction",
"object": "session"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
}
},
{
"name": "abortTransaction",
"object": "session"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll"
}
}
},
{
"commandSucceededEvent": {
"commandName": "insert"
}
},
{
"commandStartedEvent": {
"commandName": "abortTransaction",
"databaseName": "admin",
"command": {
"abortTransaction": 1,
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "abortTransaction"
}
}
]
}
]
},
{
"description": "timeoutMS applied to withTransaction",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"insert"
],
"blockConnection": true,
"blockTimeMS": 60
}
}
}
},
{
"name": "withTransaction",
"object": "session",
"arguments": {
"callback": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session",
"document": {
"_id": 1
}
},
"expectError": {
"isTimeoutError": true
}
}
]
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "insert",
"databaseName": "test",
"command": {
"insert": "coll",
"maxTimeMS": {
"$$type": [
"int",
"long"
]
}
}
}
},
{
"commandFailedEvent": {
"commandName": "insert"
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,422 @@
{
"description": "timeoutMS behaves correctly for tailable awaitData cursors",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 10
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"createOptions": {
"capped": true,
"size": 500
},
"documents": [
{
"_id": 0
},
{
"_id": 1
}
]
}
],
"tests": [
{
"description": "error if timeoutMode is cursor_lifetime",
"operations": [
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "cursorLifetime",
"cursorType": "tailableAwait"
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "error if maxAwaitTimeMS is greater than timeoutMS",
"operations": [
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailableAwait",
"timeoutMS": 5,
"maxAwaitTimeMS": 10
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "error if maxAwaitTimeMS is equal to timeoutMS",
"operations": [
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailableAwait",
"timeoutMS": 5,
"maxAwaitTimeMS": 5
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "timeoutMS applied to find",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailableAwait"
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"tailable": true,
"awaitData": true,
"maxTimeMS": {
"$$exists": true
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore if maxAwaitTimeMS is not set",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "createFindCursor",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailableAwait",
"timeoutMS": 20,
"batchSize": 1
},
"saveResultAsEntity": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"tailable": true,
"awaitData": true,
"maxTimeMS": {
"$$exists": true
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore if maxAwaitTimeMS is set",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "createFindCursor",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailableAwait",
"timeoutMS": 20,
"batchSize": 1,
"maxAwaitTimeMS": 1
},
"saveResultAsEntity": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"tailable": true,
"awaitData": true,
"maxTimeMS": {
"$$exists": true
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": 1
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore - failure",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "createFindCursor",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailableAwait",
"batchSize": 1
},
"saveResultAsEntity": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor",
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"tailable": true,
"awaitData": true,
"maxTimeMS": {
"$$exists": true
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll"
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,312 @@
{
"description": "timeoutMS behaves correctly for tailable non-awaitData cursors",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "4.4"
}
],
"createEntities": [
{
"client": {
"id": "failPointClient",
"useMultipleMongoses": false
}
},
{
"client": {
"id": "client",
"uriOptions": {
"timeoutMS": 10
},
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "coll"
}
}
],
"initialData": [
{
"collectionName": "coll",
"databaseName": "test",
"createOptions": {
"capped": true,
"size": 500
},
"documents": [
{
"_id": 0
},
{
"_id": 1
}
]
}
],
"tests": [
{
"description": "error if timeoutMode is cursor_lifetime",
"operations": [
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"timeoutMode": "cursorLifetime",
"cursorType": "tailable"
},
"expectError": {
"isClientError": true
}
}
]
},
{
"description": "timeoutMS applied to find",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"find"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "find",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailable"
},
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"tailable": true,
"awaitData": {
"$$exists": false
},
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore - success",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"find",
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "createFindCursor",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailable",
"timeoutMS": 20,
"batchSize": 1
},
"saveResultAsEntity": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"tailable": true,
"awaitData": {
"$$exists": false
},
"maxTimeMS": {
"$$exists": false
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
},
{
"description": "timeoutMS is refreshed for getMore - failure",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "failPointClient",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"getMore"
],
"blockConnection": true,
"blockTimeMS": 15
}
}
}
},
{
"name": "createFindCursor",
"object": "collection",
"arguments": {
"filter": {},
"cursorType": "tailable",
"batchSize": 1
},
"saveResultAsEntity": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "tailableCursor",
"expectError": {
"isTimeoutError": true
}
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"commandName": "find",
"databaseName": "test",
"command": {
"find": "coll",
"tailable": true,
"awaitData": {
"$$exists": false
},
"maxTimeMS": {
"$$exists": false
}
}
}
},
{
"commandStartedEvent": {
"commandName": "getMore",
"databaseName": "test",
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "coll",
"maxTimeMS": {
"$$exists": false
}
}
}
}
]
}
]
}
]
}

32
test/test_csot.py Normal file
View File

@ -0,0 +1,32 @@
# Copyright 2022-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Test the CSOT unified spec tests."""
import os
import sys
sys.path[0:0] = [""]
from test import unittest
from test.unified_format import generate_test_classes
# Location of JSON test specifications.
TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "csot")
# Generate unified tests.
globals().update(generate_test_classes(TEST_PATH, module=__name__))
if __name__ == "__main__":
unittest.main()

View File

@ -395,7 +395,7 @@ class TestIntegration(SpecRunner):
"""Run the recordPrimary test operation."""
self._previous_primary = self.scenario_client.primary
def wait_for_primary_change(self, timeout_ms):
def wait_for_primary_change(self, timeout):
"""Run the waitForPrimaryChange test operation."""
def primary_changed():
@ -404,7 +404,6 @@ class TestIntegration(SpecRunner):
return False
return primary != self._previous_primary
timeout = timeout_ms / 1000.0
wait_until(primary_changed, "change primary", timeout=timeout)
def wait(self, ms):

View File

@ -0,0 +1,159 @@
{
"runOn": [
{
"minServerVersion": "4.2",
"topology": [
"replicaset",
"sharded"
]
}
],
"database_name": "transaction-tests",
"collection_name": "test",
"data": [],
"tests": [
{
"description": "add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors",
"clientOptions": {
"socketTimeoutMS": 100
},
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"commitTransaction"
],
"blockConnection": true,
"blockTimeMS": 150
}
},
"operations": [
{
"name": "startTransaction",
"object": "session0"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "commitTransaction",
"object": "session0",
"result": {
"errorLabelsContain": [
"RetryableWriteError",
"UnknownTransactionCommitResult"
],
"errorLabelsOmit": [
"TransientTransactionError"
]
}
},
{
"name": "commitTransaction",
"object": "session0"
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"readConcern": null,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "insert",
"database_name": "transaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": null,
"autocommit": false,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": null,
"autocommit": false,
"writeConcern": {
"w": "majority",
"wtimeout": 10000
}
},
"command_name": "commitTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": null,
"autocommit": false,
"writeConcern": {
"w": "majority",
"wtimeout": 10000
}
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
}
]
}
}
}
]
}

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -0,0 +1,39 @@
{
"description": "collectionData-createOptions-type",
"schemaVersion": "1.9",
"createEntities": [
{
"client": {
"id": "client0"
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "foo"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo"
}
}
],
"initialData": [
{
"collectionName": "foo",
"databaseName": "foo",
"createOptions": 0,
"documents": []
}
],
"tests": [
{
"description": "foo",
"operations": []
}
]
}

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -18,8 +18,7 @@
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "foo",
"foo": 0
"collectionName": "foo"
}
}
],

View File

@ -0,0 +1,27 @@
{
"description": "collectionOrDatabaseOptions-timeoutMS-type",
"schemaVersion": "1.9",
"createEntities": [
{
"client": {
"id": "client0"
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "foo",
"databaseOptions": {
"timeoutMS": 4.5
}
}
}
],
"tests": [
{
"description": "foo",
"operations": []
}
]
}

View File

@ -0,0 +1,25 @@
{
"description": "expectedError-isTimeoutError-type",
"schemaVersion": "1.9",
"createEntities": [
{
"client": {
"id": "client0"
}
}
],
"tests": [
{
"description": "foo",
"operations": [
{
"name": "foo",
"object": "client0",
"expectError": {
"isTimeoutError": 0
}
}
]
}
]
}

View File

@ -0,0 +1,24 @@
{
"description": "expectedEventsForClient-ignoreExtraEvents-type",
"schemaVersion": "1.7",
"createEntities": [
{
"client": {
"id": "client0"
}
}
],
"tests": [
{
"description": "foo",
"operations": [],
"expectEvents": [
{
"client": "client0",
"events": [],
"ignoreExtraEvents": 0
}
]
}
]
}

View File

@ -0,0 +1,68 @@
{
"description": "collectionData-createOptions",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "3.6"
}
],
"createEntities": [
{
"client": {
"id": "client0"
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "database0"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "database0",
"createOptions": {
"capped": true,
"size": 512
},
"documents": [
{
"_id": 1,
"x": 11
}
]
}
],
"tests": [
{
"description": "collection is created with the correct options",
"operations": [
{
"name": "runCommand",
"object": "database0",
"arguments": {
"commandName": "collStats",
"command": {
"collStats": "coll0",
"scale": 1
}
},
"expectResult": {
"capped": true,
"maxSize": 512
}
}
]
}
]
}

View File

@ -0,0 +1,74 @@
{
"description": "createEntities-operation",
"schemaVersion": "1.9",
"tests": [
{
"description": "createEntities operation",
"operations": [
{
"name": "createEntities",
"object": "testRunner",
"arguments": {
"entities": [
{
"client": {
"id": "client1",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database1",
"client": "client1",
"databaseName": "database1"
}
},
{
"collection": {
"id": "collection1",
"database": "database1",
"collectionName": "coll1"
}
}
]
}
},
{
"name": "deleteOne",
"object": "collection1",
"arguments": {
"filter": {
"_id": 1
}
}
}
],
"expectEvents": [
{
"client": "client1",
"events": [
{
"commandStartedEvent": {
"command": {
"delete": "coll1",
"deletes": [
{
"q": {
"_id": 1
},
"limit": 1
}
]
},
"commandName": "delete",
"databaseName": "database1"
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,108 @@
{
"description": "entity-cursor-iterateOnce",
"schemaVersion": "1.9",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "database0"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"databaseName": "database0",
"collectionName": "coll0",
"documents": [
{
"_id": 1
},
{
"_id": 2
},
{
"_id": 3
}
]
}
],
"tests": [
{
"description": "iterateOnce",
"operations": [
{
"name": "createFindCursor",
"object": "collection0",
"arguments": {
"filter": {},
"batchSize": 2
},
"saveResultAsEntity": "cursor0"
},
{
"name": "iterateUntilDocumentOrError",
"object": "cursor0",
"expectResult": {
"_id": 1
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "cursor0",
"expectResult": {
"_id": 2
}
},
{
"name": "iterateOnce",
"object": "cursor0"
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"find": "coll0",
"filter": {},
"batchSize": 2
},
"commandName": "find",
"databaseName": "database0"
}
},
{
"commandStartedEvent": {
"command": {
"getMore": {
"$$type": "long"
},
"collection": "coll0"
},
"commandName": "getMore"
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"description": "matches-lte-operator",
"schemaVersion": "1.9",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "database0Name"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "database0Name",
"documents": []
}
],
"tests": [
{
"description": "special lte matching operator",
"operations": [
{
"name": "insertOne",
"object": "collection0",
"arguments": {
"document": {
"_id": 1,
"y": 1
}
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"insert": "coll0",
"documents": [
{
"_id": {
"$$lte": 1
},
"y": {
"$$lte": 2
}
}
]
},
"commandName": "insert",
"databaseName": "database0Name"
}
}
]
}
]
}
]
}

View File

@ -42,6 +42,7 @@ from test.utils import (
from test.version import Version
from typing import Any
import pymongo
from bson import SON, Code, DBRef, Decimal128, Int64, MaxKey, MinKey, json_util
from bson.binary import Binary
from bson.objectid import ObjectId
@ -56,9 +57,13 @@ from pymongo.errors import (
BulkWriteError,
ConfigurationError,
ConnectionFailure,
ExecutionTimeout,
InvalidOperation,
NetworkTimeout,
NotPrimaryError,
PyMongoError,
ServerSelectionTimeoutError,
WriteConcernError,
)
from pymongo.monitoring import (
_SENSITIVE_COMMANDS,
@ -198,11 +203,16 @@ def parse_bulk_write_error_result(error):
class NonLazyCursor(object):
"""A find cursor proxy that creates the remote cursor when initialized."""
def __init__(self, find_cursor):
def __init__(self, find_cursor, client):
self.client = client
self.find_cursor = find_cursor
# Create the server side cursor.
self.first_result = next(find_cursor, None)
@property
def alive(self):
return self.first_result is not None or self.find_cursor.alive
def __next__(self):
if self.first_result is not None:
first = self.first_result
@ -210,8 +220,12 @@ class NonLazyCursor(object):
return first
return next(self.find_cursor)
# Added to support the iterateOnce operation.
try_next = __next__
def close(self):
self.find_cursor.close()
self.client = None
class EventListenerUtil(CMAPListener, CommandListener):
@ -520,6 +534,11 @@ class MatchEvaluatorUtil(object):
expected_lsid = self.test.entity_map.get_lsid_for_session(spec)
self.test.assertEqual(expected_lsid, actual[key_to_compare])
def _operation_lte(self, spec, actual, key_to_compare):
if key_to_compare not in actual:
self.test.fail(f"Actual command is missing the {key_to_compare} field: {spec}")
self.test.assertLessEqual(actual[key_to_compare], spec)
def _evaluate_special_operation(self, opname, spec, actual, key_to_compare):
method_name = "_operation_%s" % (opname.strip("$"),)
try:
@ -710,7 +729,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
a class attribute ``TEST_SPEC``.
"""
SCHEMA_VERSION = Version.from_string("1.7")
SCHEMA_VERSION = Version.from_string("1.9")
RUN_ON_LOAD_BALANCER = True
RUN_ON_SERVERLESS = True
TEST_SPEC: Any
@ -730,6 +749,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
for i, collection_data in enumerate(initial_data):
coll_name = collection_data["collectionName"]
db_name = collection_data["databaseName"]
opts = collection_data.get("createOptions", {})
documents = collection_data["documents"]
# Setup the collection with as few majority writes as possible.
@ -741,10 +761,12 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
else:
wc = WriteConcern(w=1)
if documents:
if opts:
db.create_collection(coll_name, **opts)
db.get_collection(coll_name, write_concern=wc).insert_many(documents)
else:
# Ensure collection exists
db.create_collection(coll_name, write_concern=wc)
db.create_collection(coll_name, write_concern=wc, **opts)
@classmethod
def setUpClass(cls):
@ -782,9 +804,26 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
"Dirty explicit session is discarded" in spec["description"]
or "Dirty implicit session is discarded" in spec["description"]
):
raise unittest.SkipTest("MMAPv1 does not support retryWrites=True")
self.skipTest("MMAPv1 does not support retryWrites=True")
elif "Client side error in command starting transaction" in spec["description"]:
raise unittest.SkipTest("Implement PYTHON-1894")
self.skipTest("Implement PYTHON-1894")
class_name = self.__class__.__name__.lower()
description = spec["description"].lower()
if "csot" in class_name:
if "change" in description or "change" in class_name:
self.skipTest("CSOT not implemented for watch()")
if "cursors" in class_name:
self.skipTest("CSOT not implemented for cursors")
if "tailable" in class_name:
self.skipTest("CSOT not implemented for tailable cursors")
if "sessions" in class_name:
self.skipTest("CSOT not implemented for sessions")
if "withtransaction" in description:
self.skipTest("CSOT not implemented for with_transaction")
if "transaction" in class_name or "transaction" in description:
self.skipTest("CSOT not implemented for transactions")
if "socket timeout" in description:
self.skipTest("CSOT not implemented for socket timeouts")
# Some tests need to be skipped based on the operations they try to run.
for op in spec["operations"]:
@ -801,10 +840,21 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
if not client_context.test_commands_enabled:
if name == "failPoint" or name == "targetedFailPoint":
self.skipTest("Test commands must be enabled to use fail points")
if "timeoutMode" in op.get("arguments", {}):
self.skipTest("PyMongo does not support timeoutMode")
if name == "createEntities":
self.maybe_skip_entity(op.get("arguments", {}).get("entities", []))
def maybe_skip_entity(self, entities):
for entity in entities:
entity_type = next(iter(entity))
if entity_type == "bucket":
self.skipTest("GridFS is not currently supported (PYTHON-2459)")
def process_error(self, exception, spec):
is_error = spec.get("isError")
is_client_error = spec.get("isClientError")
is_timeout_error = spec.get("isTimeoutError")
error_contains = spec.get("errorContains")
error_code = spec.get("errorCode")
error_code_name = spec.get("errorCodeName")
@ -825,6 +875,15 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
else:
self.assertNotIsInstance(exception, PyMongoError)
if is_timeout_error:
# TODO: PYTHON-3291 Implement error transformation.
if isinstance(exception, WriteConcernError):
self.assertEqual(exception.code, 50)
else:
self.assertIsInstance(
exception, (NetworkTimeout, ExecutionTimeout, ServerSelectionTimeoutError)
)
if error_contains:
if isinstance(exception, BulkWriteError):
errmsg = str(exception.details).lower()
@ -925,15 +984,21 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
self.__raise_if_unsupported("find", target, Collection)
if "filter" not in kwargs:
self.fail('createFindCursor requires a "filter" argument')
cursor = NonLazyCursor(target.find(*args, **kwargs))
cursor = NonLazyCursor(target.find(*args, **kwargs), target.database.client)
self.addCleanup(cursor.close)
return cursor
def _collectionOperation_count(self, target, *args, **kwargs):
self.skipTest("PyMongo does not support collection.count()")
def _collectionOperation_listIndexes(self, target, *args, **kwargs):
if "batch_size" in kwargs:
self.skipTest("PyMongo does not support batch_size for list_indexes")
return target.list_indexes(*args, **kwargs)
def _collectionOperation_listIndexNames(self, target, *args, **kwargs):
self.skipTest("PyMongo does not support list_index_names")
def _sessionOperation_withTransaction(self, target, *args, **kwargs):
if client_context.storage_engine == "mmapv1":
self.skipTest("MMAPv1 does not support document-level locking")
@ -946,13 +1011,21 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
self.__raise_if_unsupported("startTransaction", target, ClientSession)
return target.start_transaction(*args, **kwargs)
def _cursor_iterateOnce(self, target, *args, **kwargs):
self.__raise_if_unsupported("iterateOnce", target, NonLazyCursor, ChangeStream)
return target.try_next()
def _changeStreamOperation_iterateUntilDocumentOrError(self, target, *args, **kwargs):
self.__raise_if_unsupported("iterateUntilDocumentOrError", target, ChangeStream)
return next(target)
def _cursor_iterateUntilDocumentOrError(self, target, *args, **kwargs):
self.__raise_if_unsupported("iterateUntilDocumentOrError", target, NonLazyCursor)
return next(target)
while target.alive:
try:
return next(target)
except StopIteration:
pass
def _cursor_close(self, target, *args, **kwargs):
self.__raise_if_unsupported("close", target, NonLazyCursor)
@ -960,6 +1033,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
def run_entity_operation(self, spec):
target = self.entity_map[spec["object"]]
client = target
opname = spec["name"]
opargs = spec.get("arguments")
expect_error = spec.get("expectError")
@ -977,20 +1051,26 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
spec, arguments, camel_to_snake(opname), self.entity_map, self.run_operations
)
else:
arguments = tuple()
arguments = {}
if isinstance(target, MongoClient):
method_name = "_clientOperation_%s" % (opname,)
client = target
elif isinstance(target, Database):
method_name = "_databaseOperation_%s" % (opname,)
client = target.client
elif isinstance(target, Collection):
method_name = "_collectionOperation_%s" % (opname,)
client = target.database.client
elif isinstance(target, ChangeStream):
method_name = "_changeStreamOperation_%s" % (opname,)
client = target._client
elif isinstance(target, NonLazyCursor):
method_name = "_cursor_%s" % (opname,)
client = target.client
elif isinstance(target, ClientSession):
method_name = "_sessionOperation_%s" % (opname,)
client = target._client
elif isinstance(target, GridFSBucket):
raise NotImplementedError
else:
@ -1007,7 +1087,17 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
cmd = functools.partial(method, target)
try:
result = cmd(**dict(arguments))
# TODO: PYTHON-3289 apply inherited timeout by default.
inherit_timeout = getattr(target, "timeout", None)
# CSOT: Translate the spec test "timeout" arg into pymongo's context timeout API.
if "timeout" in arguments or inherit_timeout is not None:
timeout = arguments.pop("timeout", None)
if timeout is None:
timeout = inherit_timeout
with pymongo.timeout(timeout):
result = cmd(**dict(arguments))
else:
result = cmd(**dict(arguments))
except Exception as exc:
# Ignore all operation errors but to avoid masking bugs don't
# ignore things like TypeError and ValueError.
@ -1057,6 +1147,9 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
self.addCleanup(client.close)
self.__set_fail_point(client=client, command_args=spec["failPoint"])
def _testOperation_createEntities(self, spec):
self.entity_map.create_entities_from_spec(spec["entities"], uri=self._uri)
def _testOperation_assertSessionTransactionState(self, spec):
session = self.entity_map[spec["session"]]
expected_state = getattr(_TxnState, spec["state"].upper())
@ -1245,6 +1338,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
raise unittest.SkipTest("%s" % (skip_reason,))
# process createEntities
self._uri = uri
self.entity_map = EntityMapUtil(self)
self.entity_map.create_entities_from_spec(self.TEST_SPEC.get("createEntities", []), uri=uri)
# process initialData
@ -1309,7 +1403,7 @@ def generate_test_classes(
class_name_prefix="",
expected_failures=[], # noqa: B006
bypass_test_generation_errors=False,
**kwargs
**kwargs,
):
"""Method for generating test classes. Returns a dictionary where keys are
the names of test classes and values are the test class objects."""

View File

@ -2,7 +2,7 @@
"tests": [
{
"description": "Valid connection and timeout options are parsed correctly",
"uri": "mongodb://example.com/?appname=URI-OPTIONS-SPEC-TEST&connectTimeoutMS=20000&heartbeatFrequencyMS=5000&localThresholdMS=3000&maxIdleTimeMS=50000&replicaSet=uri-options-spec&retryWrites=true&serverSelectionTimeoutMS=15000&socketTimeoutMS=7500",
"uri": "mongodb://example.com/?appname=URI-OPTIONS-SPEC-TEST&connectTimeoutMS=20000&heartbeatFrequencyMS=5000&localThresholdMS=3000&maxIdleTimeMS=50000&replicaSet=uri-options-spec&retryWrites=true&serverSelectionTimeoutMS=15000&socketTimeoutMS=7500&timeoutMS=100",
"valid": true,
"warning": false,
"hosts": null,
@ -16,7 +16,8 @@
"replicaSet": "uri-options-spec",
"retryWrites": true,
"serverSelectionTimeoutMS": 15000,
"socketTimeoutMS": 7500
"socketTimeoutMS": 7500,
"timeoutMS": 100
}
},
{
@ -238,6 +239,35 @@
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "timeoutMS=0",
"uri": "mongodb://example.com/?timeoutMS=0",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"timeoutMS": 0
}
},
{
"description": "Non-numeric timeoutMS causes a warning",
"uri": "mongodb://example.com/?timeoutMS=invalid",
"valid": true,
"warning": true,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "Too low timeoutMS causes a warning",
"uri": "mongodb://example.com/?timeoutMS=-2",
"valid": true,
"warning": true,
"hosts": null,
"auth": null,
"options": {}
}
]
}

View File

@ -44,15 +44,6 @@
"tlsAllowInvalidCertificates": true
}
},
{
"description": "Invalid tlsAllowInvalidCertificates causes a warning",
"uri": "mongodb://example.com/?tlsAllowInvalidCertificates=invalid",
"valid": true,
"warning": true,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsAllowInvalidHostnames is parsed correctly",
"uri": "mongodb://example.com/?tlsAllowInvalidHostnames=true",

View File

@ -35,6 +35,7 @@ from bson.objectid import ObjectId
from bson.son import SON
from pymongo import MongoClient, monitoring, operations, read_preferences
from pymongo.collection import ReturnDocument
from pymongo.cursor import CursorType
from pymongo.errors import ConfigurationError, OperationFailure
from pymongo.hello import HelloCompat
from pymongo.monitoring import _SENSITIVE_COMMANDS
@ -651,6 +652,9 @@ def parse_collection_options(opts):
if "readConcern" in opts:
opts["read_concern"] = ReadConcern(**dict(opts.pop("readConcern")))
if "timeoutMS" in opts:
opts["timeout"] = int(opts.pop("timeoutMS")) / 1000.0
return opts
@ -988,6 +992,10 @@ def parse_spec_options(opts):
if "readConcern" in opts:
opts["read_concern"] = ReadConcern(**dict(opts.pop("readConcern")))
if "timeoutMS" in opts:
assert isinstance(opts["timeoutMS"], int)
opts["timeout"] = int(opts.pop("timeoutMS")) / 1000.0
if "maxTimeMS" in opts:
opts["max_time_ms"] = opts.pop("maxTimeMS")
@ -1041,6 +1049,8 @@ def prepare_spec_arguments(spec, arguments, opname, entity_map, with_txn_callbac
# Aggregate uses "batchSize", while find uses batch_size.
elif (arg_name == "batchSize" or arg_name == "allowDiskUse") and opname == "aggregate":
continue
elif arg_name == "timeoutMode":
raise unittest.SkipTest("PyMongo does not support timeoutMode")
# Requires boolean returnDocument.
elif arg_name == "returnDocument":
arguments[c2s] = getattr(ReturnDocument, arguments.pop(arg_name).upper())
@ -1090,5 +1100,13 @@ def prepare_spec_arguments(spec, arguments, opname, entity_map, with_txn_callbac
arguments["index_or_name"] = arguments.pop(arg_name)
elif opname == "rename" and arg_name == "to":
arguments["new_name"] = arguments.pop(arg_name)
elif arg_name == "cursorType":
cursor_type = arguments.pop(arg_name)
if cursor_type == "tailable":
arguments["cursor_type"] = CursorType.TAILABLE
elif cursor_type == "tailableAwait":
arguments["cursor_type"] = CursorType.TAILABLE
else:
assert False, f"Unsupported cursorType: {cursor_type}"
else:
arguments[c2s] = arguments.pop(arg_name)