Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
.. Created by changelog.py at 2025-04-08, command
.. Created by changelog.py at 2025-04-15, command
'/Users/giffler/.cache/pre-commit/repoecmh3ah8/py_env-python3.12/bin/changelog docs/source/changes compile --categories Added Changed Fixed Security Deprecated --output=docs/source/changelog.rst'
based on the format of 'https://keepachangelog.com/'

#########
CHANGELOG
#########

[Unreleased] - 2025-04-08
[Unreleased] - 2025-04-15
=========================

Added
Expand Down
2 changes: 1 addition & 1 deletion tardis/utilities/executors/sshexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ async def bounded_connection(self):
:py:class:`~asyncssh.SSHClientConnection`
so that only `MaxSessions` commands run at once.
"""
if self._ssh_connection is None:
if self._ssh_connection is None or self._session_bound is None:
async with self.lock:
# check that connection has not been initialized in a different task
while self._ssh_connection is None:
Expand Down
30 changes: 30 additions & 0 deletions tests/utilities_t/executors_t/test_sshexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,36 @@ async def force_connection():
# make sure the connection is not needlessly replaced
self.assertEqual(self.executor._ssh_connection, current_ssh_connection)

def test_connection_race(self):
# see https://github.com/MatterMiners/tardis/issues/369
waiter = asyncio.Event()

async def mocked_probe_max_session(connection):
await waiter.wait()
return 10

async def run_bounded_connection():
async with self.executor.bounded_connection as connection:
return connection

async def run_race_condition():
first_connection = asyncio.ensure_future(run_bounded_connection())
await asyncio.sleep(0.1) # give some time to hit the waiter
self.assertIsNotNone(self.executor._ssh_connection)
self.assertIsNone(self.executor._session_bound)
second_connection = asyncio.ensure_future(run_bounded_connection())
await asyncio.sleep(0.1) # give some time to schedule the second tasks
waiter.set()
# check that no new connection is established
self.assertEqual(await first_connection, await second_connection)

# monkey patch prob session
with patch(
"tardis.utilities.executors.sshexecutor.probe_max_session",
mocked_probe_max_session,
):
run_async(run_race_condition)

def test_lock(self):
self.assertIsInstance(self.executor.lock, asyncio.Lock)

Expand Down