Skip to content

Commit b2d4c22

Browse files
authored
Merge pull request #233 from dcermak/use-path.unlink
Tolerate already deleted lockfiles when releasing the lock
2 parents 1e19e12 + 2e6560e commit b2d4c22

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

pytest_container/container.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,14 @@ def launch_container(self) -> None:
10921092
def release_lock() -> None:
10931093
_logger.debug("Releasing lock %s", lock.lock_file)
10941094
lock.release()
1095-
os.unlink(lock.lock_file)
1095+
# we're fine with another process/thread having deleted the
1096+
# lockfile, as long as the locking was thread safe
1097+
try:
1098+
# no we can't use Path.unlink(missing_ok=True) here, as the kw
1099+
# argument is not present in Python < 3.8
1100+
os.unlink(lock.lock_file)
1101+
except FileNotFoundError:
1102+
pass
10961103

10971104
# Container preparation can fail, but then we would never release the
10981105
# lock as release_lock is not yet in self._stack. However, we do not

tests/test_container.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# pylint: disable=missing-function-docstring,missing-module-docstring
22
from pathlib import Path
3+
from tempfile import gettempdir
34
from typing import Optional
45
from typing import Union
56

67
import pytest
78
from pytest_container import Container
89
from pytest_container import DerivedContainer
10+
from pytest_container.container import ContainerLauncher
911
from pytest_container.container import ImageFormat
1012
from pytest_container.runtime import OciRuntimeBase
1113

@@ -76,6 +78,39 @@ def test_lockfile_unique() -> None:
7678
assert cont1.filelock_filename != cont2.filelock_filename
7779

7880

81+
def test_removed_lockfile_does_not_kill_launcher(
82+
container_runtime: OciRuntimeBase, pytestconfig: pytest.Config
83+
) -> None:
84+
"""Test that the container launcher doesn't die if the container lockfile
85+
got removed by another thread.
86+
87+
It can happen in certain scenarios that a ``Container`` is launched from two
88+
concurrently running test functions. These test functions will use the same
89+
lockfile. A nasty data race can occur, where both test functions unlock the
90+
lockfile nearly at the same time, but then only one of them can succeed in
91+
removing it and the other test inadvertently fails. This is a regression
92+
test, that such a situation is tolerated and doesn't cause a failure.
93+
94+
In this test we create a singleton container where we utilize that the
95+
lockfile is removed in ``__exit__()``. We hence already delete the lockfile
96+
in the ``with`` block and provoke a failure in ``__exit__()``.
97+
98+
See also https://github.com/dcermak/pytest_container/issues/232.
99+
100+
"""
101+
cont = Container(url=images.LEAP_URL, singleton=True)
102+
103+
with ContainerLauncher.from_pytestconfig(
104+
cont, container_runtime, pytestconfig
105+
) as launcher:
106+
launcher.launch_container()
107+
108+
lockfile_abspath = Path(gettempdir()) / cont.filelock_filename
109+
assert lockfile_abspath.exists
110+
111+
lockfile_abspath.unlink()
112+
113+
79114
def test_derived_container_build_tag(
80115
container_runtime: OciRuntimeBase, pytestconfig: pytest.Config
81116
) -> None:

0 commit comments

Comments
 (0)