Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d23ed12467 | ||
|
|
d6248e9cf1 | ||
|
|
3f04a62f68 | ||
|
|
8da192f9ca | ||
|
|
56b6b6dbc2 | ||
|
|
449d0f316c | ||
|
|
e04576de22 | ||
|
|
cf1c6a11f7 | ||
|
|
d29b2b7cf4 | ||
|
|
0477b9bc0c | ||
|
|
ecad17d24e | ||
|
|
485e0a5e56 | ||
|
|
995365c712 |
@ -12,7 +12,7 @@ stepback: true
|
|||||||
# Actual testing tasks are marked with `type: test`
|
# Actual testing tasks are marked with `type: test`
|
||||||
command_type: system
|
command_type: system
|
||||||
|
|
||||||
# Protect ourself against rogue test case, or curl gone wild, that runs forever
|
# Protect ourselves against rogue test case, or curl gone wild, that runs forever
|
||||||
# Good rule of thumb: the averageish length a task takes, times 5
|
# Good rule of thumb: the averageish length a task takes, times 5
|
||||||
# That roughly accounts for variable system performance for various buildvariants
|
# That roughly accounts for variable system performance for various buildvariants
|
||||||
exec_timeout_secs: 3600 # 60 minutes is the longest we'll ever run (primarily
|
exec_timeout_secs: 3600 # 60 minutes is the longest we'll ever run (primarily
|
||||||
@ -1058,7 +1058,7 @@ task_groups:
|
|||||||
- command: expansions.update
|
- command: expansions.update
|
||||||
params:
|
params:
|
||||||
file: src/atlas-expansion.yml
|
file: src/atlas-expansion.yml
|
||||||
teardown_group:
|
teardown_task:
|
||||||
- command: subprocess.exec
|
- command: subprocess.exec
|
||||||
params:
|
params:
|
||||||
working_dir: src
|
working_dir: src
|
||||||
@ -1098,7 +1098,7 @@ task_groups:
|
|||||||
- command: expansions.update
|
- command: expansions.update
|
||||||
params:
|
params:
|
||||||
file: src/atlas-expansion.yml
|
file: src/atlas-expansion.yml
|
||||||
teardown_group:
|
teardown_task:
|
||||||
- command: subprocess.exec
|
- command: subprocess.exec
|
||||||
params:
|
params:
|
||||||
working_dir: src
|
working_dir: src
|
||||||
@ -1174,6 +1174,7 @@ tasks:
|
|||||||
- func: "build release"
|
- func: "build release"
|
||||||
vars:
|
vars:
|
||||||
VERSION: "3.7"
|
VERSION: "3.7"
|
||||||
|
- func: "upload release"
|
||||||
|
|
||||||
- name: "release-windows"
|
- name: "release-windows"
|
||||||
tags: ["release_tag"]
|
tags: ["release_tag"]
|
||||||
|
|||||||
@ -2405,6 +2405,7 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
|
|||||||
uint32_t c_w_s_size;
|
uint32_t c_w_s_size;
|
||||||
uint32_t code_size;
|
uint32_t code_size;
|
||||||
uint32_t scope_size;
|
uint32_t scope_size;
|
||||||
|
uint32_t len;
|
||||||
PyObject* code;
|
PyObject* code;
|
||||||
PyObject* scope;
|
PyObject* scope;
|
||||||
PyObject* code_type;
|
PyObject* code_type;
|
||||||
@ -2424,7 +2425,8 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
|
|||||||
memcpy(&code_size, buffer + *position, 4);
|
memcpy(&code_size, buffer + *position, 4);
|
||||||
code_size = BSON_UINT32_FROM_LE(code_size);
|
code_size = BSON_UINT32_FROM_LE(code_size);
|
||||||
/* code_w_scope length + code length + code + scope length */
|
/* code_w_scope length + code length + code + scope length */
|
||||||
if (!code_size || max < code_size || max < 4 + 4 + code_size + 4) {
|
len = 4 + 4 + code_size + 4;
|
||||||
|
if (!code_size || max < code_size || max < len || len < code_size) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
}
|
}
|
||||||
*position += 4;
|
*position += 4;
|
||||||
@ -2442,12 +2444,9 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
|
|||||||
|
|
||||||
memcpy(&scope_size, buffer + *position, 4);
|
memcpy(&scope_size, buffer + *position, 4);
|
||||||
scope_size = BSON_UINT32_FROM_LE(scope_size);
|
scope_size = BSON_UINT32_FROM_LE(scope_size);
|
||||||
if (scope_size < BSON_MIN_SIZE) {
|
|
||||||
Py_DECREF(code);
|
|
||||||
goto invalid;
|
|
||||||
}
|
|
||||||
/* code length + code + scope length + scope */
|
/* code length + code + scope length + scope */
|
||||||
if ((4 + code_size + 4 + scope_size) != c_w_s_size) {
|
len = 4 + 4 + code_size + scope_size;
|
||||||
|
if (scope_size < BSON_MIN_SIZE || len != c_w_s_size || len < scope_size) {
|
||||||
Py_DECREF(code);
|
Py_DECREF(code);
|
||||||
goto invalid;
|
goto invalid;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ Connections to Atlas require TLS/SSL.
|
|||||||
|
|
||||||
You can read more about TLS versions and their security implications here:
|
You can read more about TLS versions and their security implications here:
|
||||||
|
|
||||||
`<https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html#only-support-strong-protocols>`_
|
`<https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html#only-support-strong-protocols>`_
|
||||||
|
|
||||||
.. _python.org: https://www.python.org/downloads/
|
.. _python.org: https://www.python.org/downloads/
|
||||||
.. _homebrew: https://brew.sh/
|
.. _homebrew: https://brew.sh/
|
||||||
|
|||||||
@ -1,6 +1,52 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
Changes in Version 4.6.3
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
PyMongo 4.6.3 fixes the following bug:
|
||||||
|
|
||||||
|
- Fixed a potential memory access violation when decoding invalid bson.
|
||||||
|
|
||||||
|
Issues Resolved
|
||||||
|
...............
|
||||||
|
|
||||||
|
See the `PyMongo 4.6.3 release notes in JIRA`_ for the list of resolved issues
|
||||||
|
in this release.
|
||||||
|
|
||||||
|
.. _PyMongo 4.6.3 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=38360
|
||||||
|
|
||||||
|
Changes in Version 4.6.2
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
PyMongo 4.6.2 fixes the following bug:
|
||||||
|
|
||||||
|
- Fixed a bug appearing in Python 3.12 where "RuntimeError: can't create new thread at interpreter shutdown"
|
||||||
|
could be written to stderr when a MongoClient's thread starts as the python interpreter is shutting down.
|
||||||
|
|
||||||
|
Issues Resolved
|
||||||
|
...............
|
||||||
|
|
||||||
|
See the `PyMongo 4.6.2 release notes in JIRA`_ for the list of resolved issues
|
||||||
|
in this release.
|
||||||
|
|
||||||
|
.. _PyMongo 4.6.2 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=37906
|
||||||
|
|
||||||
|
Changes in Version 4.6.1
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
PyMongo 4.6.1 fixes the following bug:
|
||||||
|
|
||||||
|
- Ensure retryable read ``OperationFailure`` errors re-raise exception when 0 or NoneType error code is provided.
|
||||||
|
|
||||||
|
Issues Resolved
|
||||||
|
...............
|
||||||
|
|
||||||
|
See the `PyMongo 4.6.1 release notes in JIRA`_ for the list of resolved issues
|
||||||
|
in this release.
|
||||||
|
|
||||||
|
.. _PyMongo 4.6.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=37138
|
||||||
|
|
||||||
Changes in Version 4.6
|
Changes in Version 4.6
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@ -33,6 +79,14 @@ PyMongo 4.6 brings a number of improvements including:
|
|||||||
- Added the :ref:`network-compression-example` documentation page.
|
- Added the :ref:`network-compression-example` documentation page.
|
||||||
- Added more timeout information to network errors.
|
- Added more timeout information to network errors.
|
||||||
|
|
||||||
|
Issues Resolved
|
||||||
|
...............
|
||||||
|
|
||||||
|
See the `PyMongo 4.6 release notes in JIRA`_ for the list of resolved issues
|
||||||
|
in this release.
|
||||||
|
|
||||||
|
.. _PyMongo 4.6 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=36542
|
||||||
|
|
||||||
Changes in Version 4.5
|
Changes in Version 4.5
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|||||||
@ -59,4 +59,4 @@ deprecated PyMongo features.
|
|||||||
|
|
||||||
.. _the warnings module: https://docs.python.org/3/library/warnings.html
|
.. _the warnings module: https://docs.python.org/3/library/warnings.html
|
||||||
|
|
||||||
.. _the -W command line option: https://docs.python.org/3/using/cmdline.html#cmdoption-w
|
.. _the -W command line option: https://docs.python.org/3/using/cmdline.html#cmdoption-W
|
||||||
|
|||||||
@ -32,7 +32,7 @@ MongoDB.
|
|||||||
|
|
||||||
You can read more about TLS versions and their security implications here:
|
You can read more about TLS versions and their security implications here:
|
||||||
|
|
||||||
`<https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html#only-support-strong-protocols>`_
|
`<https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html#only-support-strong-protocols>`_
|
||||||
|
|
||||||
.. _python.org: https://www.python.org/downloads/
|
.. _python.org: https://www.python.org/downloads/
|
||||||
.. _homebrew: https://brew.sh/
|
.. _homebrew: https://brew.sh/
|
||||||
|
|||||||
@ -44,7 +44,7 @@ MincePy
|
|||||||
workflow as possible.
|
workflow as possible.
|
||||||
|
|
||||||
Ming
|
Ming
|
||||||
`Ming <http://merciless.sourceforge.net/>`_ (the Merciless) is a
|
`Ming <https://ming.readthedocs.io/en/latest/>`_ is a
|
||||||
library that allows you to enforce schemas on a MongoDB database in
|
library that allows you to enforce schemas on a MongoDB database in
|
||||||
your Python application. It was developed by `SourceForge
|
your Python application. It was developed by `SourceForge
|
||||||
<http://sourceforge.net/>`_ in the course of their migration to
|
<http://sourceforge.net/>`_ in the course of their migration to
|
||||||
|
|||||||
@ -17,7 +17,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Tuple, Union
|
from typing import Tuple, Union
|
||||||
|
|
||||||
version_tuple: Tuple[Union[int, str], ...] = (4, 6, 0)
|
version_tuple: Tuple[Union[int, str], ...] = (4, 6, 4, ".dev0")
|
||||||
|
|
||||||
|
|
||||||
def get_version_string() -> str:
|
def get_version_string() -> str:
|
||||||
|
|||||||
@ -2898,7 +2898,7 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
|
|||||||
.. seealso:: The MongoDB documentation on `changeStreams <https://mongodb.com/docs/manual/changeStreams/>`_.
|
.. seealso:: The MongoDB documentation on `changeStreams <https://mongodb.com/docs/manual/changeStreams/>`_.
|
||||||
|
|
||||||
.. _change streams specification:
|
.. _change streams specification:
|
||||||
https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst
|
https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md
|
||||||
"""
|
"""
|
||||||
return CollectionChangeStream(
|
return CollectionChangeStream(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -666,7 +666,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
|
|||||||
.. seealso:: The MongoDB documentation on `changeStreams <https://mongodb.com/docs/manual/changeStreams/>`_.
|
.. seealso:: The MongoDB documentation on `changeStreams <https://mongodb.com/docs/manual/changeStreams/>`_.
|
||||||
|
|
||||||
.. _change streams specification:
|
.. _change streams specification:
|
||||||
https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst
|
https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md
|
||||||
"""
|
"""
|
||||||
return DatabaseChangeStream(
|
return DatabaseChangeStream(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -1030,7 +1030,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
|
|||||||
.. seealso:: The MongoDB documentation on `changeStreams <https://mongodb.com/docs/manual/changeStreams/>`_.
|
.. seealso:: The MongoDB documentation on `changeStreams <https://mongodb.com/docs/manual/changeStreams/>`_.
|
||||||
|
|
||||||
.. _change streams specification:
|
.. _change streams specification:
|
||||||
https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst
|
https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md
|
||||||
"""
|
"""
|
||||||
return ClusterChangeStream(
|
return ClusterChangeStream(
|
||||||
self.admin,
|
self.admin,
|
||||||
@ -2329,7 +2329,8 @@ class _ClientConnectionRetryable(Generic[T]):
|
|||||||
# ConnectionFailures do not supply a code property
|
# ConnectionFailures do not supply a code property
|
||||||
exc_code = getattr(exc, "code", None)
|
exc_code = getattr(exc, "code", None)
|
||||||
if self._is_not_eligible_for_retry() or (
|
if self._is_not_eligible_for_retry() or (
|
||||||
exc_code and exc_code not in helpers._RETRYABLE_ERROR_CODES
|
isinstance(exc, OperationFailure)
|
||||||
|
and exc_code not in helpers._RETRYABLE_ERROR_CODES
|
||||||
):
|
):
|
||||||
raise
|
raise
|
||||||
self._retrying = True
|
self._retrying = True
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import weakref
|
import weakref
|
||||||
@ -92,7 +93,15 @@ class PeriodicExecutor:
|
|||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
self._thread = weakref.proxy(thread)
|
self._thread = weakref.proxy(thread)
|
||||||
_register_executor(self)
|
_register_executor(self)
|
||||||
thread.start()
|
# Mitigation to RuntimeError firing when thread starts on shutdown
|
||||||
|
# https://github.com/python/cpython/issues/114570
|
||||||
|
try:
|
||||||
|
thread.start()
|
||||||
|
except RuntimeError as e:
|
||||||
|
if "interpreter shutdown" in str(e) or sys.is_finalizing():
|
||||||
|
self._thread = None
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
|
||||||
def close(self, dummy: Any = None) -> None:
|
def close(self, dummy: Any = None) -> None:
|
||||||
"""Stop. To restart, call open().
|
"""Stop. To restart, call open().
|
||||||
|
|||||||
@ -266,8 +266,14 @@ class TopologyDescription:
|
|||||||
def _apply_local_threshold(self, selection: Optional[Selection]) -> list[ServerDescription]:
|
def _apply_local_threshold(self, selection: Optional[Selection]) -> list[ServerDescription]:
|
||||||
if not selection:
|
if not selection:
|
||||||
return []
|
return []
|
||||||
|
round_trip_times: list[float] = []
|
||||||
|
for server in selection.server_descriptions:
|
||||||
|
if server.round_trip_time is None:
|
||||||
|
config_err_msg = f"round_trip_time for server {server.address} is unexpectedly None: {self}, servers: {selection.server_descriptions}"
|
||||||
|
raise ConfigurationError(config_err_msg)
|
||||||
|
round_trip_times.append(server.round_trip_time)
|
||||||
# Round trip time in seconds.
|
# Round trip time in seconds.
|
||||||
fastest = min(cast(float, s.round_trip_time) for s in selection.server_descriptions)
|
fastest = min(round_trip_times)
|
||||||
threshold = self._topology_settings.local_threshold_ms / 1000.0
|
threshold = self._topology_settings.local_threshold_ms / 1000.0
|
||||||
return [
|
return [
|
||||||
s
|
s
|
||||||
|
|||||||
@ -16,11 +16,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from test import PyMongoTestCase
|
||||||
|
|
||||||
from mockupdb import MockupDB, OpMsg, going
|
from mockupdb import MockupDB, OpMsg, going
|
||||||
|
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
|
from pymongo.errors import OperationFailure
|
||||||
|
|
||||||
|
|
||||||
class TestCursor(unittest.TestCase):
|
class TestCursor(unittest.TestCase):
|
||||||
@ -57,5 +59,31 @@ class TestCursor(unittest.TestCase):
|
|||||||
request.replies({"cursor": {"id": cursor_id, "nextBatch": [{}]}})
|
request.replies({"cursor": {"id": cursor_id, "nextBatch": [{}]}})
|
||||||
|
|
||||||
|
|
||||||
|
class TestRetryableErrorCodeCatch(PyMongoTestCase):
|
||||||
|
def _test_fail_on_operation_failure_with_code(self, code):
|
||||||
|
"""Test reads on error codes that should not be retried"""
|
||||||
|
server = MockupDB()
|
||||||
|
server.run()
|
||||||
|
self.addCleanup(server.stop)
|
||||||
|
server.autoresponds("ismaster", maxWireVersion=6)
|
||||||
|
|
||||||
|
client = MongoClient(server.uri)
|
||||||
|
|
||||||
|
with going(lambda: server.receives(OpMsg({"find": "collection"})).command_err(code=code)):
|
||||||
|
cursor = client.db.collection.find()
|
||||||
|
with self.assertRaises(OperationFailure) as ctx:
|
||||||
|
cursor.next()
|
||||||
|
self.assertEqual(ctx.exception.code, code)
|
||||||
|
|
||||||
|
def test_fail_on_operation_failure_none(self):
|
||||||
|
self._test_fail_on_operation_failure_with_code(None)
|
||||||
|
|
||||||
|
def test_fail_on_operation_failure_zero(self):
|
||||||
|
self._test_fail_on_operation_failure_with_code(0)
|
||||||
|
|
||||||
|
def test_fail_on_operation_failure_one(self):
|
||||||
|
self._test_fail_on_operation_failure_with_code(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
@ -79,6 +80,17 @@ class TestMonitor(IntegrationTest):
|
|||||||
for executor in executors:
|
for executor in executors:
|
||||||
wait_until(lambda: executor._stopped, f"closed executor: {executor._name}", timeout=5)
|
wait_until(lambda: executor._stopped, f"closed executor: {executor._name}", timeout=5)
|
||||||
|
|
||||||
|
def test_no_thread_start_runtime_err_on_shutdown(self):
|
||||||
|
"""Test we silence noisy runtime errors fired when the MongoClient spawns a new thread
|
||||||
|
on process shutdown."""
|
||||||
|
command = [sys.executable, "-c", "from pymongo import MongoClient; c = MongoClient()"]
|
||||||
|
completed_process: subprocess.CompletedProcess = subprocess.run(
|
||||||
|
command, capture_output=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(completed_process.stderr)
|
||||||
|
self.assertFalse(completed_process.stdout)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user