Skip to content

Commit

Permalink
Version bump to 0.2.0, supporting to switch from shared lock to exclu…
Browse files Browse the repository at this point in the history
…sive lock.
  • Loading branch information
jmfernandez committed Aug 2, 2024
1 parent 158227b commit 37d9987
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 21 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,25 @@ using shared resources, like a caching directory or a SQLite database. All of th
can use the resources, but only one should update them.

The library is being used in several INB projects.

## Example of a shared lock using context:

```python
import datetime
import os
import time

from RWFileLock import RWFileLock

lock = RWFileLock("/tmp/rwfilelock.lock")
with lock.shared_lock():
print(
f"[{datetime.datetime.now().isoformat()}] Got shlock {os.getpid()}"
)

time.sleep(7)
print(
f"[{datetime.datetime.now().isoformat()}] Releasing shlock {os.getpid()}"
)

```
2 changes: 1 addition & 1 deletion RWFileLock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)

# https://www.python.org/dev/peps/pep-0396/
__version__ = version = "0.1.2"
__version__ = version = "0.2.0"
__author__ = "José M. Fernández <https://orcid.org/0000-0002-4806-5140>"
__copyright__ = "© 2020-2024 Barcelona Supercomputing Center (BSC), ES"
__license__ = "LGPLv2"
Expand Down
104 changes: 84 additions & 20 deletions RWFileLock/rw_file_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
import tempfile
import fcntl

from typing import TYPE_CHECKING
from typing import (
cast,
TYPE_CHECKING,
)

if TYPE_CHECKING:
from typing import (
Expand All @@ -35,50 +38,79 @@
Union,
)

from typing_extensions import (
Protocol,
)

class FilenoProtocol(Protocol):
def fileno(self) -> "int":
...


class LockError(Exception):
def __init__(self, message: "str"):
super().__init__(message)


class RWFileLock(object):
def __init__(self, filename: "Union[str, os.PathLike[str]]"):
self.lock_fd = os.open(filename, (os.O_RDWR | os.O_CREAT), mode=0o700)
def __init__(self, filename: "Union[str, os.PathLike[str], int, FilenoProtocol]"):
if hasattr(filename, "fileno") and callable(getattr(filename, "fileno")):
self.lock_fd = filename.fileno()
self.should_close = False
elif isinstance(filename, int):
self.lock_fd = filename
self.should_close = False
else:
self.lock_fd = os.open(
cast("Union[str, os.PathLike[str]]", filename),
(os.O_RDWR | os.O_CREAT),
mode=0o700,
)
self.should_close = True

self.isLocked = False
self.isShareLock = False

def r_lock(self) -> "None":
if self.isLocked:
if self.isLocked and self.isShareLock:
raise LockError("Already locked by ourselves")

try:
fcntl.lockf(self.lock_fd, (fcntl.LOCK_SH | fcntl.LOCK_NB))
self.isLocked = True
self.isShareLock = True
except IOError:
raise LockError("Already locked by others")

def r_blocking_lock(self) -> "None":
if self.isLocked:
if self.isLocked and self.isShareLock:
raise LockError("Already locked by ourselves")

fcntl.lockf(self.lock_fd, fcntl.LOCK_SH)
self.isLocked = True
self.isShareLock = True

def w_lock(self) -> "None":
if self.isLocked:
if self.isLocked and not self.isShareLock:
raise LockError("Already locked by ourselves")

try:
fcntl.lockf(self.lock_fd, (fcntl.LOCK_EX | fcntl.LOCK_NB))
self.isLocked = True
self.isShareLock = False
except IOError:
raise LockError("Already locked by others")

def w_blocking_lock(self) -> "None":
if self.isLocked:
if self.isLocked and not self.isShareLock:
raise LockError("Already locked by ourselves")

fcntl.lockf(self.lock_fd, fcntl.LOCK_EX)
self.isLocked = True
try:
fcntl.lockf(self.lock_fd, fcntl.LOCK_EX)
self.isLocked = True
self.isShareLock = False
except OSError:
raise LockError("Read only file descriptor?")

def unlock(self) -> "None":
if self.isLocked:
Expand Down Expand Up @@ -124,29 +156,61 @@ def exclusive_blocking_lock(self) -> "Iterator[None]":
self.unlock()

def __del__(self) -> "None":
try:
os.close(self.lock_fd)
except:
pass
if self.should_close:
try:
os.close(self.lock_fd)
except:
pass


if __name__ == "__main__":
lock = RWFileLock("/tmp/rwfilelock.lock")
# fH = open("/etc/passwd", mode="r")
# lock = RWFileLock(fH)

print("Trying getting lock")
import datetime
import sys
import time

print(f"[{datetime.datetime.now().isoformat()}] Trying getting lock {os.getpid()}")

sys.stdout.flush()
try:
if len(sys.argv) > 1:
if len(sys.argv) == 3:
lock.r_blocking_lock()
print(f"[{datetime.datetime.now().isoformat()}] Got shlock {os.getpid()}")
time.sleep(5)
print(
f"[{datetime.datetime.now().isoformat()}] Trying to switch exlock {os.getpid()}"
)
lock.w_blocking_lock()
print(f"[{datetime.datetime.now().isoformat()}] Got exlock {os.getpid()}")
time.sleep(3)
print(
f"[{datetime.datetime.now().isoformat()}] Releasing exlock {os.getpid()}"
)
lock.unlock()
elif len(sys.argv) == 2:
with lock.exclusive_lock():
import time
print(
f"[{datetime.datetime.now().isoformat()}] Got exlock {os.getpid()}"
)

time.sleep(10)
print(
f"[{datetime.datetime.now().isoformat()}] Releasing exlock {os.getpid()}"
)
else:
with lock.shared_lock():
import time

time.sleep(10)
print(
f"[{datetime.datetime.now().isoformat()}] Got shlock {os.getpid()}"
)

time.sleep(7)
print(
f"[{datetime.datetime.now().isoformat()}] Releasing shlock {os.getpid()}"
)
except LockError:
print("Unable to get lock")
print(
f"[{datetime.datetime.now().isoformat()}] Unable to get lock {os.getpid()}"
)
1 change: 1 addition & 0 deletions mypy-requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
types-setuptools
typing_extensions

0 comments on commit 37d9987

Please sign in to comment.