diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad7b341cc..e5d83be63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -200,7 +200,7 @@ the pages will re-render and the browser will automatically refresh. - Append `test/.py::::` to run specific tests. You can omit the `` to test a full class and the `` to test a full module. For example: - `tox -m test test/test_change_stream.py::TestUnifiedChangeStreamsErrors::test_change_stream_errors_on_ElectionInProgress`. + `tox -m test -- test/test_change_stream.py::TestUnifiedChangeStreamsErrors::test_change_stream_errors_on_ElectionInProgress`. - Use the `-k` argument to select tests by pattern. ## Running Load Balancer Tests Locally diff --git a/doc/changelog.rst b/doc/changelog.rst index d4791bbe7..9a840e6e2 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -85,6 +85,21 @@ Unavoidable breaking changes - The "aws" extra now requires minimum version of ``1.1.0`` for ``pymongo_auth_aws``. +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 ------------------------ @@ -93,6 +108,14 @@ 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 ------------------------ @@ -100,6 +123,14 @@ 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 ---------------------- @@ -132,6 +163,14 @@ PyMongo 4.6 brings a number of improvements including: - Added the :ref:`network-compression-example` documentation page. - 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 ---------------------- diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index 9bead2988..f2076b087 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -879,8 +879,11 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): # This will be used later if we fork. MongoClient._clients[self._topology._topology_id] = self - def _init_background(self) -> None: + def _init_background(self, old_pid: Optional[int] = None) -> None: self._topology = Topology(self._topology_settings) + # Seed the topology with the old one's pid so we can detect clients + # that are opened before a fork and used after. + self._topology._pid = old_pid def target() -> bool: client = self_ref() @@ -903,7 +906,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): def _after_fork(self) -> None: """Resets topology in a child after successfully forking.""" - self._init_background() + self._init_background(self._topology._pid) def _duplicate(self, **kwargs: Any) -> MongoClient: args = self.__init_kwargs.copy() @@ -921,7 +924,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): the server may change. In such cases, store a local reference to a ServerDescription first, then use its properties. """ - server = self._topology.select_server(writable_server_selector, _Op.TEST) + server = self._get_topology().select_server(writable_server_selector, _Op.TEST) return getattr(server.description, attr_name) diff --git a/pymongo/topology.py b/pymongo/topology.py index 239a4a430..99adcae62 100644 --- a/pymongo/topology.py +++ b/pymongo/topology.py @@ -20,9 +20,11 @@ import logging import os import queue import random +import sys import time import warnings import weakref +from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, cast from pymongo import _csot, common, helpers, periodic_executor @@ -70,6 +72,9 @@ if TYPE_CHECKING: from pymongo.typings import ClusterTime, _Address +_pymongo_dir = str(Path(__file__).parent) + + def process_events_queue(queue_ref: weakref.ReferenceType[queue.Queue]) -> bool: q = queue_ref() if not q: @@ -187,12 +192,17 @@ class Topology: self._pid = pid elif pid != self._pid: self._pid = pid - warnings.warn( + if sys.version_info[:2] >= (3, 12): + kwargs = {"skip_file_prefixes": (_pymongo_dir,)} + else: + kwargs = {"stacklevel": 6} + # Ignore B028 warning for missing stacklevel. + warnings.warn( # type: ignore[call-overload] # noqa: B028 "MongoClient opened before fork. May not be entirely fork-safe, " "proceed with caution. See PyMongo's documentation for details: " "https://pymongo.readthedocs.io/en/stable/faq.html#" "is-pymongo-fork-safe", - stacklevel=2, + **kwargs, ) with self._lock: # Close servers and clear the pools. diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index f7da8c278..72b0f8a02 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -20,8 +20,6 @@ import socketserver import sys import threading -from pymongo.monitoring import ServerHeartbeatFailedEvent, ServerHeartbeatStartedEvent - sys.path[0:0] = [""] from test import IntegrationTest, unittest @@ -52,6 +50,7 @@ from pymongo.errors import ( ) from pymongo.hello import Hello, HelloCompat from pymongo.helpers import _check_command_response, _check_write_command_response +from pymongo.monitoring import ServerHeartbeatFailedEvent, ServerHeartbeatStartedEvent from pymongo.server_description import SERVER_TYPE, ServerDescription from pymongo.settings import TopologySettings from pymongo.topology import Topology, _ErrorContext @@ -408,17 +407,21 @@ class MockTCPHandler(socketserver.BaseRequestHandler): self.request.close() -class TestHeartbeatStartOrdering(unittest.TestCase): - def start_server(self, events): - server = socketserver.TCPServer(("localhost", 9999), MockTCPHandler) - server.events = events - server.handle_request() - server.server_close() +class TCPServer(socketserver.TCPServer): + allow_reuse_address = True + def handle_request_and_shutdown(self): + self.handle_request() + self.server_close() + + +class TestHeartbeatStartOrdering(unittest.TestCase): def test_heartbeat_start_ordering(self): events = [] listener = HeartbeatEventsListListener(events) - server_thread = threading.Thread(target=self.start_server, args=(events,)) + server = TCPServer(("localhost", 9999), MockTCPHandler) + server.events = events + server_thread = threading.Thread(target=server.handle_request_and_shutdown) server_thread.start() _c = MongoClient( "mongodb://localhost:9999", serverSelectionTimeoutMS=500, event_listeners=(listener,) diff --git a/test/test_encryption.py b/test/test_encryption.py index 77a1c9a52..e91560099 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -26,6 +26,7 @@ import sys import textwrap import traceback import uuid +import warnings from threading import Thread from typing import Any, Dict, Mapping @@ -348,7 +349,9 @@ class TestClientSimple(EncryptionIntegrationTest): self.addCleanup(client.close) def target(): - client.admin.command("ping") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + client.admin.command("ping") with self.fork(target): target() diff --git a/test/test_fork.py b/test/test_fork.py index 7b19e4cd8..d9ac3d261 100644 --- a/test/test_fork.py +++ b/test/test_fork.py @@ -18,6 +18,7 @@ from __future__ import annotations import os import sys import unittest +import warnings from multiprocessing import Pipe sys.path[0:0] = [""] @@ -43,7 +44,9 @@ class TestFork(IntegrationTest): with self.client._MongoClient__lock: def target(): - self.client.admin.command("ping") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.client.admin.command("ping") with self.fork(target): pass @@ -72,7 +75,11 @@ class TestFork(IntegrationTest): parent_cursor_exc = self.client._kill_cursors_executor def target(): - self.client.admin.command("ping") + # Catch the fork warning and send to the parent for assertion. + with warnings.catch_warnings(record=True) as ctx: + warnings.simplefilter("always") + self.client.admin.command("ping") + child_conn.send(str(ctx[0])) child_conn.send(self.client._topology._pid) child_conn.send( ( @@ -83,6 +90,8 @@ class TestFork(IntegrationTest): with self.fork(target): self.assertEqual(self.client._topology._pid, init_id) + fork_warning = parent_conn.recv() + self.assertIn("MongoClient opened before fork", fork_warning) child_id = parent_conn.recv() self.assertNotEqual(child_id, init_id) passed, msg = parent_conn.recv()