Skip to content

Bugfix/147 #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 26, 2023
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Changelog
=========
v3.10.6 (2023-03-25)
--------------------
- Enhance the robustness of the try/catch block in _soft.py. by :user:`jahrules`.

v3.10.5 (2023-03-25)
--------------------
- Add explicit error check as certain UNIX filesystems do not support flock. by :user:`jahrules`.
Expand Down
18 changes: 8 additions & 10 deletions src/filelock/_soft.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import os
import sys
from errno import EACCES, EEXIST, ENOENT
from errno import EACCES, EEXIST

from ._api import BaseFileLock
from ._util import raise_on_exist_ro_file
Expand All @@ -21,17 +21,15 @@ def _acquire(self) -> None:
| os.O_TRUNC # truncate the file to zero byte
)
try:
fd = os.open(self._lock_file, flags, self._mode)
except OSError as exception:
if exception.errno == EEXIST: # expected if cannot lock
pass
elif exception.errno == ENOENT: # No such file or directory - parent directory is missing
file_handler = os.open(self._lock_file, flags, self._mode)
except OSError as exception: # re-raise unless expected exception
if not (
exception.errno == EEXIST # lock already exist
or (exception.errno == EACCES and sys.platform == "win32") # has no access to this lock
): # pragma: win32 no cover
raise
elif exception.errno == EACCES and sys.platform != "win32": # pragma: win32 no cover
# Permission denied - parent dir is R/O
raise # note windows does not allow you to make a folder r/o only files
else:
self._lock_file_fd = fd
self._lock_file_fd = file_handler

def _release(self) -> None:
os.close(self._lock_file_fd) # type: ignore # the lock file is definitely not None
Expand Down
27 changes: 16 additions & 11 deletions tests/test_filelock.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def __init__(self, target: Callable[[], None], name: str) -> None:
def run(self) -> None:
try:
super().run()
except Exception: # pragma: no cover
except RuntimeError: # pragma: no cover
self.ex = sys.exc_info() # pragma: no cover

def join(self, timeout: float | None = None) -> None:
Expand Down Expand Up @@ -337,8 +337,8 @@ def test_context_release_on_exc(lock_type: type[BaseFileLock], tmp_path: Path) -
with lock as lock_1:
assert lock is lock_1
assert lock.is_locked
raise Exception
except Exception:
raise ValueError
except ValueError:
assert not lock.is_locked


Expand All @@ -352,8 +352,8 @@ def test_acquire_release_on_exc(lock_type: type[BaseFileLock], tmp_path: Path) -
with lock.acquire() as lock_1:
assert lock is lock_1
assert lock.is_locked
raise Exception
except Exception:
raise ValueError
except ValueError:
assert not lock.is_locked


Expand Down Expand Up @@ -385,9 +385,8 @@ def test_del(lock_type: type[BaseFileLock], tmp_path: Path) -> None:
def test_cleanup_soft_lock(tmp_path: Path) -> None:
# tests if the lock file is removed after use
lock_path = tmp_path / "a"
lock = SoftFileLock(str(lock_path))

with lock:
with SoftFileLock(lock_path):
assert lock_path.exists()
assert not lock_path.exists()

Expand All @@ -399,9 +398,9 @@ def test_poll_intervall_deprecated(lock_type: type[BaseFileLock], tmp_path: Path

with pytest.deprecated_call(match="use poll_interval instead of poll_intervall") as checker:
lock.acquire(poll_intervall=0.05) # the deprecation warning will be captured by the checker
frameinfo = getframeinfo(stack()[0][0]) # get frameinfo of current file and lineno (+1 than the above lineno)
frame_info = getframeinfo(stack()[0][0]) # get frame info of current file and lineno (+1 than the above lineno)
for warning in checker:
if warning.filename == frameinfo.filename and warning.lineno + 1 == frameinfo.lineno: # pragma: no cover
if warning.filename == frame_info.filename and warning.lineno + 1 == frame_info.lineno: # pragma: no cover
break
else: # pragma: no cover
pytest.fail("No warnings of stacklevel=2 matching.")
Expand Down Expand Up @@ -490,7 +489,7 @@ def test_wrong_platform(tmp_path: Path) -> None:
assert inspect.isabstract(BaseFileLock)

lock_type = UnixFileLock if sys.platform == "win32" else WindowsFileLock
lock = lock_type(str(tmp_path / "lockfile"))
lock = lock_type(tmp_path / "lockfile")

with pytest.raises(NotImplementedError):
lock.acquire()
Expand All @@ -502,5 +501,11 @@ def test_wrong_platform(tmp_path: Path) -> None:
def test_flock_not_implemented_unix(tmp_path: Path, mocker: MockerFixture) -> None:
mocker.patch("fcntl.flock", side_effect=OSError(ENOSYS, "mock error"))
with pytest.raises(NotImplementedError):
with FileLock(str(tmp_path / "a.lock")):
with FileLock(tmp_path / "a.lock"):
pass


def test_soft_errors(tmp_path: Path, mocker: MockerFixture) -> None:
mocker.patch("os.open", side_effect=OSError(ENOSYS, "mock error"))
with pytest.raises(OSError, match="mock error"):
SoftFileLock(tmp_path / "a.lock").acquire()
1 change: 0 additions & 1 deletion whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ enosys
extlinks
filelock
filemode
frameinfo
fspath
getframeinfo
intersphinx
Expand Down