PYTHON-4018 Clarify exactly what code/label fields drivers should inspect to determine retryability (#1489)

This commit is contained in:
Steven Silvester 2024-01-29 12:58:15 -06:00 committed by GitHub
parent 5877be9623
commit 85b0c0e3c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 320 additions and 14 deletions

View File

@ -83,6 +83,7 @@ from pymongo.errors import (
PyMongoError,
ServerSelectionTimeoutError,
WaitQueueTimeoutError,
WriteConcernError,
)
from pymongo.lock import _HAS_REGISTER_AT_FORK, _create_lock, _release_locks
from pymongo.monitoring import ConnectionClosedReason
@ -2141,7 +2142,7 @@ def _retryable_error_doc(exc: PyMongoError) -> Optional[Mapping[str, Any]]:
return None
def _add_retryable_write_error(exc: PyMongoError, max_wire_version: int) -> None:
def _add_retryable_write_error(exc: PyMongoError, max_wire_version: int, is_mongos: bool) -> None:
doc = _retryable_error_doc(exc)
if doc:
code = doc.get("code", 0)
@ -2158,7 +2159,10 @@ def _add_retryable_write_error(exc: PyMongoError, max_wire_version: int) -> None
for label in doc.get("errorLabels", []):
exc._add_error_label(label)
else:
if code in helpers._RETRYABLE_ERROR_CODES:
# Do not consult writeConcernError for pre-4.4 mongos.
if isinstance(exc, WriteConcernError) and is_mongos:
pass
elif code in helpers._RETRYABLE_ERROR_CODES:
exc._add_error_label("RetryableWriteError")
# Connection errors are always retryable except NotPrimaryError and WaitQueueTimeoutError which is
@ -2392,12 +2396,14 @@ class _ClientConnectionRetryable(Generic[T]):
"""
try:
max_wire_version = 0
is_mongos = False
self._server = self._get_server()
supports_session = (
self._session is not None and self._server.description.retryable_writes_supported
)
with self._client._checkout(self._server, self._session) as conn:
max_wire_version = conn.max_wire_version
is_mongos = conn.is_mongos
if self._retryable and not supports_session:
# A retry is not possible because this server does
# not support sessions raise the last error.
@ -2408,7 +2414,7 @@ class _ClientConnectionRetryable(Generic[T]):
if not self._retryable:
raise
# Add the RetryableWriteError label, if applicable.
_add_retryable_write_error(exc, max_wire_version)
_add_retryable_write_error(exc, max_wire_version, is_mongos)
raise
def _read(self) -> T:

View File

@ -3,10 +3,16 @@
"schemaVersion": "1.0",
"runOnRequirements": [
{
"minServerVersion": "3.6",
"minServerVersion": "4.0",
"topologies": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topologies": [
"sharded"
]
}
],
"createEntities": [
@ -55,16 +61,7 @@
"description": "InsertOne succeeds after retryable writeConcernError",
"runOnRequirements": [
{
"minServerVersion": "4.0",
"topologies": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topologies": [
"sharded-replicaset"
]
"minServerVersion": "4.3.1"
}
],
"operations": [
@ -168,6 +165,309 @@
]
}
]
},
{
"description": "RetryableWriteError label is added based on top-level code in pre-4.4 server response",
"runOnRequirements": [
{
"minServerVersion": "4.2",
"maxServerVersion": "4.2.99",
"topologies": [
"replicaset",
"sharded"
]
}
],
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "client0",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"insert"
],
"errorCode": 189
}
}
}
},
{
"name": "insertOne",
"object": "collection0",
"arguments": {
"document": {
"_id": 3,
"x": 33
}
},
"expectError": {
"errorLabelsContain": [
"RetryableWriteError"
]
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"insert": "coll",
"documents": [
{
"_id": 3,
"x": 33
}
]
},
"commandName": "insert",
"databaseName": "retryable-writes-tests"
}
},
{
"commandStartedEvent": {
"command": {
"insert": "coll",
"documents": [
{
"_id": 3,
"x": 33
}
]
},
"commandName": "insert",
"databaseName": "retryable-writes-tests"
}
}
]
}
],
"outcome": [
{
"collectionName": "coll",
"databaseName": "retryable-writes-tests",
"documents": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
}
]
}
]
},
{
"description": "RetryableWriteError label is added based on writeConcernError in pre-4.4 mongod response",
"runOnRequirements": [
{
"minServerVersion": "4.2",
"maxServerVersion": "4.2.99",
"topologies": [
"replicaset"
]
}
],
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "client0",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"insert"
],
"writeConcernError": {
"code": 91,
"errmsg": "Replication is being shut down"
}
}
}
}
},
{
"name": "insertOne",
"object": "collection0",
"arguments": {
"document": {
"_id": 3,
"x": 33
}
},
"expectError": {
"errorLabelsContain": [
"RetryableWriteError"
]
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"insert": "coll",
"documents": [
{
"_id": 3,
"x": 33
}
]
},
"commandName": "insert",
"databaseName": "retryable-writes-tests"
}
},
{
"commandStartedEvent": {
"command": {
"insert": "coll",
"documents": [
{
"_id": 3,
"x": 33
}
]
},
"commandName": "insert",
"databaseName": "retryable-writes-tests"
}
}
]
}
],
"outcome": [
{
"collectionName": "coll",
"databaseName": "retryable-writes-tests",
"documents": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
}
]
}
]
},
{
"description": "RetryableWriteError label is not added based on writeConcernError in pre-4.4 mongos response",
"runOnRequirements": [
{
"minServerVersion": "4.2",
"maxServerVersion": "4.2.99",
"topologies": [
"sharded"
]
}
],
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "client0",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"insert"
],
"writeConcernError": {
"code": 91,
"errmsg": "Replication is being shut down"
}
}
}
}
},
{
"name": "insertOne",
"object": "collection0",
"arguments": {
"document": {
"_id": 3,
"x": 33
}
},
"expectError": {
"errorLabelsOmit": [
"RetryableWriteError"
]
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"insert": "coll",
"documents": [
{
"_id": 3,
"x": 33
}
]
},
"commandName": "insert",
"databaseName": "retryable-writes-tests"
}
}
]
}
],
"outcome": [
{
"collectionName": "coll",
"databaseName": "retryable-writes-tests",
"documents": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
}
]
}
]
}
]
}