Skip to content

Commit

Permalink
add flockd and test; user can choose whether blocking and create lock…
Browse files Browse the repository at this point in the history
… file
  • Loading branch information
shinny-taojiachun committed Dec 28, 2023
1 parent bec76c3 commit 79ddc68
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ __pycache__/
# due to using tox and pytest
.tox
.cache
env/
venv/
3 changes: 0 additions & 3 deletions src/sample/__init__.py

This file was deleted.

2 changes: 0 additions & 2 deletions src/sample/simple.py

This file was deleted.

1 change: 1 addition & 0 deletions src/shinny_filelock/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._flockd import flocked
31 changes: 31 additions & 0 deletions src/shinny_filelock/_flockd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import fcntl
import os
from contextlib import contextmanager


@contextmanager
def flocked(path, blocking=False, create_file=False):
"""
:param path: file path
:param blocking: blocking lock
:param create_file: create file if not exists
"""
fd = -1
try:
fd_flags = os.O_RDONLY | os.O_NOCTTY
if create_file:
fd_flags |= os.O_CREAT
fd = os.open(path, fd_flags)
if fd == -1:
raise ValueError()
try:
flags = fcntl.LOCK_EX
if not blocking:
flags |= fcntl.LOCK_NB
fcntl.flock(fd, flags)
yield
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
finally:
if fd != -1:
os.close(fd)
34 changes: 34 additions & 0 deletions tests/_timeout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-

import signal
from contextlib import ContextDecorator


def raise_timeout(signum, frame):
raise TimeoutError()


class timeout(ContextDecorator):
"""Raises TimeoutError when the gien time in seconds elapsed.
"""

def __init__(self, seconds):
self._seconds = seconds

def __enter__(self):
if self._seconds:
self._replace_alarm_handler()
signal.setitimer(signal.ITIMER_REAL, self._seconds)
return self

def __exit__(self, exc_type, exc_value, traceback):
if self._seconds:
self._restore_alarm_handler()
signal.alarm(0)

def _replace_alarm_handler(self):
self._old_alarm_handler = signal.signal(signal.SIGALRM,
raise_timeout)

def _restore_alarm_handler(self):
signal.signal(signal.SIGALRM, self._old_alarm_handler)
43 changes: 43 additions & 0 deletions tests/test_flock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# the inclusion of the tests module is not meant to offer best practices for
# testing in general, but rather to support the `find_packages` example in
# setup.py that excludes installing the "tests" package
import unittest

from shinny_filelock import flocked

from ._timeout import timeout


class TestSimple(unittest.TestCase):

def test_non_blocking_lock(self):
local_path = "/tmp/test.lock"
with flocked(local_path):
try:
with flocked(local_path):
pass
except Exception as e:
self.assertEqual(e.__class__, BlockingIOError)

def test_blocking_lock(self):
local_path = "/tmp/test-block.lock"
with flocked(local_path, blocking=True):
try:
with timeout(2):
with flocked(local_path, blocking=True):
pass
except Exception as e:
self.assertEqual(e.__class__, TimeoutError)

def test_blocking_with_non_blocking_lock(self):
local_path = "/tmp/test-mix.lock"
with flocked(local_path, blocking=True):
try:
with flocked(local_path, blocking=False):
pass
except Exception as e:
self.assertEqual(e.__class__, BlockingIOError)


if __name__ == '__main__':
unittest.main()
17 changes: 0 additions & 17 deletions tests/test_simple.py

This file was deleted.

0 comments on commit 79ddc68

Please sign in to comment.