Skip to content

Commit 1a527ee

Browse files
ronan-merrickNoahStappaclark4life
authored
PYTHON-5904 RTT Thread Flips Between isMaster and hello (#2904)
Co-authored-by: Noah Stapp <noah.stapp@mongodb.com> Co-authored-by: Jeffrey 'Alex' Clark <aclark@aclark.net>
1 parent c1748a9 commit 1a527ee

4 files changed

Lines changed: 170 additions & 2 deletions

File tree

pymongo/asynchronous/pool.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ async def _hello(
287287
hello.logical_session_timeout_minutes is not None and hello.is_readable
288288
)
289289
self.logical_session_timeout_minutes: Optional[int] = hello.logical_session_timeout_minutes
290-
self.hello_ok = hello.hello_ok
290+
# hello_ok is set from helloOk, which is only returned from ismaster
291+
# don't overwrite this when connection switches to hello
292+
self.hello_ok = self.hello_ok or hello.hello_ok
291293
self.is_repl = hello.server_type in (
292294
SERVER_TYPE.RSPrimary,
293295
SERVER_TYPE.RSSecondary,

pymongo/synchronous/pool.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ def _hello(
287287
hello.logical_session_timeout_minutes is not None and hello.is_readable
288288
)
289289
self.logical_session_timeout_minutes: Optional[int] = hello.logical_session_timeout_minutes
290-
self.hello_ok = hello.hello_ok
290+
# hello_ok is set from helloOk, which is only returned from ismaster
291+
# don't overwrite this when connection switches to hello
292+
self.hello_ok = self.hello_ok or hello.hello_ok
291293
self.is_repl = hello.server_type in (
292294
SERVER_TYPE.RSPrimary,
293295
SERVER_TYPE.RSSecondary,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2026-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Test that connection continues to use hello after ismaster returns hello_ok"""
16+
17+
from __future__ import annotations
18+
19+
import unittest
20+
from types import SimpleNamespace
21+
22+
from pymongo.asynchronous.pool import AsyncConnection
23+
from test.asynchronous import AsyncUnitTest
24+
25+
_IS_SYNC = False
26+
27+
28+
class TestHelloLatched(AsyncUnitTest):
29+
def setUp(self):
30+
self._sent = []
31+
32+
def create_connection(self) -> AsyncConnection:
33+
"""Returns a minimal connection object for _hello"""
34+
conn = object.__new__(AsyncConnection)
35+
conn.hello_ok = False
36+
conn.performed_handshake = True
37+
conn.opts = SimpleNamespace(server_api=None, load_balanced=False, _credentials=None)
38+
39+
return conn
40+
41+
async def test_hello_is_latched(self):
42+
"""
43+
Regression Test for PYTHON-5904
44+
Tests for connection hello_ok persistence when connection
45+
Switches from ismaster to hello
46+
"""
47+
48+
async def mock_conn_command(db, cmd, **kwargs):
49+
"""Returns mocked hello and ismaster results for conn.command"""
50+
self._sent.append(cmd.copy())
51+
if cmd.get("ismaster") == 1:
52+
return {"ok": 1, "helloOk": True, "ismaster": True, "maxWireVersion": 25}
53+
return {"ok": 1, "isWritablePrimary": True, "maxWireVersion": 25}
54+
55+
conn = self.create_connection()
56+
conn.command = mock_conn_command
57+
58+
# First hello
59+
await conn._hello(None, None)
60+
# Verify hello_ok is True
61+
self.assertTrue(conn.hello_ok)
62+
# Verify command sent is ismaster
63+
self.assertEqual(self._sent[0].get("ismaster"), 1)
64+
self.assertEqual(self._sent[0].get("helloOk"), True)
65+
66+
# Second hello
67+
await conn._hello(None, None)
68+
# Verify hello_ok has not changed
69+
self.assertTrue(conn.hello_ok)
70+
# Verify command sent is hello
71+
self.assertEqual(self._sent[1].get("hello"), 1)
72+
self.assertIsNone(self._sent[1].get("ismaster", None))
73+
74+
# Third hello
75+
await conn._hello(None, None)
76+
# Verify connection continues to use hello
77+
self.assertEqual(self._sent[2].get("hello"), 1)
78+
self.assertIsNone(self._sent[2].get("ismaster", None))
79+
80+
81+
if __name__ == "__main__":
82+
unittest.main()

test/test_hello_latched.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2026-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Test that connection continues to use hello after ismaster returns hello_ok"""
16+
17+
from __future__ import annotations
18+
19+
import unittest
20+
from types import SimpleNamespace
21+
22+
from pymongo.synchronous.pool import Connection
23+
from test import UnitTest
24+
25+
_IS_SYNC = True
26+
27+
28+
class TestHelloLatched(UnitTest):
29+
def setUp(self):
30+
self._sent = []
31+
32+
def create_connection(self) -> Connection:
33+
"""Returns a minimal connection object for _hello"""
34+
conn = object.__new__(Connection)
35+
conn.hello_ok = False
36+
conn.performed_handshake = True
37+
conn.opts = SimpleNamespace(server_api=None, load_balanced=False, _credentials=None)
38+
39+
return conn
40+
41+
def test_hello_is_latched(self):
42+
"""
43+
Regression Test for PYTHON-5904
44+
Tests for connection hello_ok persistence when connection
45+
Switches from ismaster to hello
46+
"""
47+
48+
def mock_conn_command(db, cmd, **kwargs):
49+
"""Returns mocked hello and ismaster results for conn.command"""
50+
self._sent.append(cmd.copy())
51+
if cmd.get("ismaster") == 1:
52+
return {"ok": 1, "helloOk": True, "ismaster": True, "maxWireVersion": 25}
53+
return {"ok": 1, "isWritablePrimary": True, "maxWireVersion": 25}
54+
55+
conn = self.create_connection()
56+
conn.command = mock_conn_command
57+
58+
# First hello
59+
conn._hello(None, None)
60+
# Verify hello_ok is True
61+
self.assertTrue(conn.hello_ok)
62+
# Verify command sent is ismaster
63+
self.assertEqual(self._sent[0].get("ismaster"), 1)
64+
self.assertEqual(self._sent[0].get("helloOk"), True)
65+
66+
# Second hello
67+
conn._hello(None, None)
68+
# Verify hello_ok has not changed
69+
self.assertTrue(conn.hello_ok)
70+
# Verify command sent is hello
71+
self.assertEqual(self._sent[1].get("hello"), 1)
72+
self.assertIsNone(self._sent[1].get("ismaster", None))
73+
74+
# Third hello
75+
conn._hello(None, None)
76+
# Verify connection continues to use hello
77+
self.assertEqual(self._sent[2].get("hello"), 1)
78+
self.assertIsNone(self._sent[2].get("ismaster", None))
79+
80+
81+
if __name__ == "__main__":
82+
unittest.main()

0 commit comments

Comments
 (0)