Skip to content

Commit 0dece2f

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver
2 parents 011969a + 708ce16 commit 0dece2f

20 files changed

+535
-63
lines changed

doc/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ PyMongo 4.12 brings a number of changes including:
2424
:class:`~pymongo.read_preferences.SecondaryPreferred`,
2525
:class:`~pymongo.read_preferences.Nearest`. Support for ``hedge`` will be removed in PyMongo 5.0.
2626
- Removed PyOpenSSL support from the asynchronous API due to limitations of the CPython asyncio.Protocol SSL implementation.
27+
- Allow valid SRV hostnames with less than 3 parts.
2728

2829
Issues Resolved
2930
...............

pymongo/asynchronous/mongo_client.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,7 @@ def __init__(
878878

879879
self._opened = False
880880
self._closed = False
881+
self._loop: Optional[asyncio.AbstractEventLoop] = None
881882
if not is_srv:
882883
self._init_background()
883884

@@ -1709,6 +1710,13 @@ async def _get_topology(self) -> Topology:
17091710
If this client was created with "connect=False", calling _get_topology
17101711
launches the connection process in the background.
17111712
"""
1713+
if not _IS_SYNC:
1714+
if self._loop is None:
1715+
self._loop = asyncio.get_running_loop()
1716+
elif self._loop != asyncio.get_running_loop():
1717+
raise RuntimeError(
1718+
"Cannot use AsyncMongoClient in different event loop. AsyncMongoClient uses low-level asyncio APIs that bind it to the event loop it was created on."
1719+
)
17121720
if not self._opened:
17131721
if self._resolve_srv_info["is_srv"]:
17141722
await self._resolve_srv()
@@ -2840,7 +2848,7 @@ async def _write(self) -> T:
28402848
_debug_log(
28412849
_COMMAND_LOGGER,
28422850
message=f"Retrying write attempt number {self._attempt_number}",
2843-
clientId=self._client.client_id,
2851+
clientId=self._client._topology_settings._topology_id,
28442852
commandName=self._operation,
28452853
operationId=self._operation_id,
28462854
)

pymongo/asynchronous/pool.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -931,13 +931,15 @@ async def remove_stale_sockets(self, reference_generation: int) -> None:
931931
return
932932

933933
if self.opts.max_idle_time_seconds is not None:
934+
close_conns = []
934935
async with self.lock:
935936
while (
936937
self.conns
937938
and self.conns[-1].idle_time_seconds() > self.opts.max_idle_time_seconds
938939
):
939-
conn = self.conns.pop()
940-
await conn.close_conn(ConnectionClosedReason.IDLE)
940+
close_conns.append(self.conns.pop())
941+
for conn in close_conns:
942+
await conn.close_conn(ConnectionClosedReason.IDLE)
941943

942944
while True:
943945
async with self.size_cond:
@@ -957,14 +959,18 @@ async def remove_stale_sockets(self, reference_generation: int) -> None:
957959
self._pending += 1
958960
incremented = True
959961
conn = await self.connect()
962+
close_conn = False
960963
async with self.lock:
961964
# Close connection and return if the pool was reset during
962965
# socket creation or while acquiring the pool lock.
963966
if self.gen.get_overall() != reference_generation:
964-
await conn.close_conn(ConnectionClosedReason.STALE)
965-
return
966-
self.conns.appendleft(conn)
967-
self.active_contexts.discard(conn.cancel_context)
967+
close_conn = True
968+
if not close_conn:
969+
self.conns.appendleft(conn)
970+
self.active_contexts.discard(conn.cancel_context)
971+
if close_conn:
972+
await conn.close_conn(ConnectionClosedReason.STALE)
973+
return
968974
finally:
969975
if incremented:
970976
# Notify after adding the socket to the pool.
@@ -1343,17 +1349,20 @@ async def checkin(self, conn: AsyncConnection) -> None:
13431349
error=ConnectionClosedReason.ERROR,
13441350
)
13451351
else:
1352+
close_conn = False
13461353
async with self.lock:
13471354
# Hold the lock to ensure this section does not race with
13481355
# Pool.reset().
13491356
if self.stale_generation(conn.generation, conn.service_id):
1350-
await conn.close_conn(ConnectionClosedReason.STALE)
1357+
close_conn = True
13511358
else:
13521359
conn.update_last_checkin_time()
13531360
conn.update_is_writable(bool(self.is_writable))
13541361
self.conns.appendleft(conn)
13551362
# Notify any threads waiting to create a connection.
13561363
self._max_connecting_cond.notify()
1364+
if close_conn:
1365+
await conn.close_conn(ConnectionClosedReason.STALE)
13571366

13581367
async with self.size_cond:
13591368
if txn:

pymongo/asynchronous/srv_resolver.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,12 @@ def __init__(
9090
raise ConfigurationError(_INVALID_HOST_MSG % ("an IP address",))
9191
except ValueError:
9292
pass
93-
9493
try:
95-
self.__plist = self.__fqdn.split(".")[1:]
94+
split_fqdn = self.__fqdn.split(".")
95+
self.__plist = split_fqdn[1:] if len(split_fqdn) > 2 else split_fqdn
9696
except Exception:
9797
raise ConfigurationError(_INVALID_HOST_MSG % (fqdn,)) from None
9898
self.__slen = len(self.__plist)
99-
if self.__slen < 2:
100-
raise ConfigurationError(_INVALID_HOST_MSG % (fqdn,))
10199

102100
async def get_options(self) -> Optional[str]:
103101
from dns import resolver
@@ -139,6 +137,10 @@ async def _get_srv_response_and_hosts(
139137

140138
# Validate hosts
141139
for node in nodes:
140+
if self.__fqdn == node[0].lower():
141+
raise ConfigurationError(
142+
"Invalid SRV host: return address is identical to SRV hostname"
143+
)
142144
try:
143145
nlist = node[0].lower().split(".")[1:][-self.__slen :]
144146
except Exception:

pymongo/asynchronous/topology.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,6 @@ async def open(self) -> None:
244244
# Close servers and clear the pools.
245245
for server in self._servers.values():
246246
await server.close()
247-
if not _IS_SYNC:
248-
self._monitor_tasks.append(server._monitor)
249247
# Reset the session pool to avoid duplicate sessions in
250248
# the child process.
251249
self._session_pool.reset()

pymongo/synchronous/mongo_client.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,7 @@ def __init__(
876876

877877
self._opened = False
878878
self._closed = False
879+
self._loop: Optional[asyncio.AbstractEventLoop] = None
879880
if not is_srv:
880881
self._init_background()
881882

@@ -1703,6 +1704,13 @@ def _get_topology(self) -> Topology:
17031704
If this client was created with "connect=False", calling _get_topology
17041705
launches the connection process in the background.
17051706
"""
1707+
if not _IS_SYNC:
1708+
if self._loop is None:
1709+
self._loop = asyncio.get_running_loop()
1710+
elif self._loop != asyncio.get_running_loop():
1711+
raise RuntimeError(
1712+
"Cannot use MongoClient in different event loop. MongoClient uses low-level asyncio APIs that bind it to the event loop it was created on."
1713+
)
17061714
if not self._opened:
17071715
if self._resolve_srv_info["is_srv"]:
17081716
self._resolve_srv()
@@ -2826,7 +2834,7 @@ def _write(self) -> T:
28262834
_debug_log(
28272835
_COMMAND_LOGGER,
28282836
message=f"Retrying write attempt number {self._attempt_number}",
2829-
clientId=self._client.client_id,
2837+
clientId=self._client._topology_settings._topology_id,
28302838
commandName=self._operation,
28312839
operationId=self._operation_id,
28322840
)

pymongo/synchronous/pool.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -927,13 +927,15 @@ def remove_stale_sockets(self, reference_generation: int) -> None:
927927
return
928928

929929
if self.opts.max_idle_time_seconds is not None:
930+
close_conns = []
930931
with self.lock:
931932
while (
932933
self.conns
933934
and self.conns[-1].idle_time_seconds() > self.opts.max_idle_time_seconds
934935
):
935-
conn = self.conns.pop()
936-
conn.close_conn(ConnectionClosedReason.IDLE)
936+
close_conns.append(self.conns.pop())
937+
for conn in close_conns:
938+
conn.close_conn(ConnectionClosedReason.IDLE)
937939

938940
while True:
939941
with self.size_cond:
@@ -953,14 +955,18 @@ def remove_stale_sockets(self, reference_generation: int) -> None:
953955
self._pending += 1
954956
incremented = True
955957
conn = self.connect()
958+
close_conn = False
956959
with self.lock:
957960
# Close connection and return if the pool was reset during
958961
# socket creation or while acquiring the pool lock.
959962
if self.gen.get_overall() != reference_generation:
960-
conn.close_conn(ConnectionClosedReason.STALE)
961-
return
962-
self.conns.appendleft(conn)
963-
self.active_contexts.discard(conn.cancel_context)
963+
close_conn = True
964+
if not close_conn:
965+
self.conns.appendleft(conn)
966+
self.active_contexts.discard(conn.cancel_context)
967+
if close_conn:
968+
conn.close_conn(ConnectionClosedReason.STALE)
969+
return
964970
finally:
965971
if incremented:
966972
# Notify after adding the socket to the pool.
@@ -1339,17 +1345,20 @@ def checkin(self, conn: Connection) -> None:
13391345
error=ConnectionClosedReason.ERROR,
13401346
)
13411347
else:
1348+
close_conn = False
13421349
with self.lock:
13431350
# Hold the lock to ensure this section does not race with
13441351
# Pool.reset().
13451352
if self.stale_generation(conn.generation, conn.service_id):
1346-
conn.close_conn(ConnectionClosedReason.STALE)
1353+
close_conn = True
13471354
else:
13481355
conn.update_last_checkin_time()
13491356
conn.update_is_writable(bool(self.is_writable))
13501357
self.conns.appendleft(conn)
13511358
# Notify any threads waiting to create a connection.
13521359
self._max_connecting_cond.notify()
1360+
if close_conn:
1361+
conn.close_conn(ConnectionClosedReason.STALE)
13531362

13541363
with self.size_cond:
13551364
if txn:

pymongo/synchronous/srv_resolver.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,12 @@ def __init__(
9090
raise ConfigurationError(_INVALID_HOST_MSG % ("an IP address",))
9191
except ValueError:
9292
pass
93-
9493
try:
95-
self.__plist = self.__fqdn.split(".")[1:]
94+
split_fqdn = self.__fqdn.split(".")
95+
self.__plist = split_fqdn[1:] if len(split_fqdn) > 2 else split_fqdn
9696
except Exception:
9797
raise ConfigurationError(_INVALID_HOST_MSG % (fqdn,)) from None
9898
self.__slen = len(self.__plist)
99-
if self.__slen < 2:
100-
raise ConfigurationError(_INVALID_HOST_MSG % (fqdn,))
10199

102100
def get_options(self) -> Optional[str]:
103101
from dns import resolver
@@ -139,6 +137,10 @@ def _get_srv_response_and_hosts(
139137

140138
# Validate hosts
141139
for node in nodes:
140+
if self.__fqdn == node[0].lower():
141+
raise ConfigurationError(
142+
"Invalid SRV host: return address is identical to SRV hostname"
143+
)
142144
try:
143145
nlist = node[0].lower().split(".")[1:][-self.__slen :]
144146
except Exception:

pymongo/synchronous/topology.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,6 @@ def open(self) -> None:
244244
# Close servers and clear the pools.
245245
for server in self._servers.values():
246246
server.close()
247-
if not _IS_SYNC:
248-
self._monitor_tasks.append(server._monitor)
249247
# Reset the session pool to avoid duplicate sessions in
250248
# the child process.
251249
self._session_pool.reset()

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ filterwarnings = [
117117
"module:unclosed <ssl.SSLSocket:ResourceWarning",
118118
"module:unclosed <socket object:ResourceWarning",
119119
"module:unclosed transport:ResourceWarning",
120+
# pytest-asyncio known issue: https://github.com/pytest-dev/pytest-asyncio/issues/724
121+
"module:unclosed event loop:ResourceWarning",
120122
# https://github.com/eventlet/eventlet/issues/818
121123
"module:please use dns.resolver.Resolver.resolve:DeprecationWarning",
122124
# https://github.com/dateutil/dateutil/issues/1314

0 commit comments

Comments
 (0)