diff --git a/changelog/70229.fixed.md b/changelog/70229.fixed.md new file mode 100644 index 000000000000..52d17792a103 --- /dev/null +++ b/changelog/70229.fixed.md @@ -0,0 +1 @@ +Fixed `salt.utils.files.await_lock` so exceptions raised inside its `async with` body are no longer converted into `FileLockError`, and the minion now logs the traceback when a job_queue lock failure occurs. diff --git a/salt/minion.py b/salt/minion.py index b1162a813814..9dcd1003846e 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -2537,6 +2537,7 @@ async def _handle_decoded_payload_impl(self, data): log.warning( "Failed to acquire job_queue lock for jid %s, queuing anyway.", data["jid"], + exc_info=True, ) # If we can't get the lock, we assume high contention and queue it to be safe. # Or we could just proceed (unsafe). Queuing is safer for stability. diff --git a/salt/utils/files.py b/salt/utils/files.py index ee8aeea1d07a..6071862290d0 100644 --- a/salt/utils/files.py +++ b/salt/utils/files.py @@ -411,6 +411,7 @@ async def await_lock(path, lock_fn=None, timeout=5, sleep=0.1, time_start=None): if time_start is None: time_start = time.time() obtained_lock = False + in_body = False def _raise_error(msg, race=False): """ @@ -459,6 +460,7 @@ def _raise_error(msg, race=False): log.trace("Write lock %s obtained", lock_fn) obtained_lock = True # Transfer control back to the code inside the with block + in_body = True yield # Exit the loop break @@ -473,6 +475,9 @@ def _raise_error(msg, race=False): raise except Exception as exc: # pylint: disable=broad-except + if in_body: + # Not a locking failure; let the caller see the real exception + raise _raise_error(f"Error encountered obtaining file lock {lock_fn}: {exc}") finally: diff --git a/tests/pytests/unit/utils/test_files.py b/tests/pytests/unit/utils/test_files.py index 6c8e96ee9c58..02cb4c41a203 100644 --- a/tests/pytests/unit/utils/test_files.py +++ b/tests/pytests/unit/utils/test_files.py @@ -474,3 +474,15 @@ def test_set_umask_nests_in_a_single_thread(): with salt.utils.files.set_umask(0o022): pass assert salt.utils.files.get_umask() == orig + + +async def test_await_lock_does_not_mask_body_exceptions(tmp_path): + """ + Exceptions raised inside the ``async with`` body must propagate unchanged + and must not be reported as a FileLockError. The lock must be released. + """ + lock_fn = str(tmp_path / "test.lock") + with pytest.raises(ValueError, match="boom"): + async with salt.utils.files.await_lock(lock_fn, lock_fn=lock_fn, timeout=1): + raise ValueError("boom") + assert not os.path.exists(lock_fn)