Coverage for gws-app/gws/lib/lock/__init__.py: 0%
72 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1"""Utilities for locking"""
3import gws
5import os
6import fcntl
7import time
10class SoftFileLock:
11 """Soft file-based locking.
13 Attempt to lock a file and yield the success status.
14 """
16 def __init__(self, path: str):
17 self.path = path
18 self.fp = None
20 def __enter__(self):
21 self.fp = _lock(self.path)
22 if not self.fp:
23 return False
24 os.write(self.fp, str(os.getpid()).encode('ascii'))
25 return True
27 def __exit__(self, exc_type, exc_val, exc_tb):
28 if self.fp:
29 _unlock(self.path, self.fp)
30 self.fp = None
33class HardFileLock:
34 """Hard file-based locking.
36 Keep attempting to lock a file until the timeout expires.
37 """
39 def __init__(self, path: str, retries: int = 10, pause: int = 2):
40 self.path = path
41 self.fp = None
42 self.retries = retries
43 self.pause = pause
45 def __enter__(self):
46 for _ in range(self.retries):
47 self.fp = _lock(self.path)
48 if self.fp:
49 return True
50 time.sleep(self.pause)
51 raise gws.Error(f'failed to lock {self.path!r}')
53 def __exit__(self, exc_type, exc_val, exc_tb):
54 if self.fp:
55 _unlock(self.path, self.fp)
56 self.fp = None
59def _lock(path):
60 try:
61 fp = os.open(path, os.O_CREAT | os.O_RDWR)
62 except OSError as exc:
63 gws.log.warning(f'lock: {path!r} open error: {exc}')
64 return
66 try:
67 fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
68 except OSError:
69 try:
70 pid = os.read(fp, 1024).decode('ascii')
71 except OSError as exc:
72 gws.log.warning(f'lock: {path!r} read error: {exc}')
73 else:
74 gws.log.info(f'lock: {path!r} locked by {pid}')
75 return
77 try:
78 os.write(fp, str(os.getpid()).encode('ascii'))
79 except OSError as exc:
80 gws.log.warning(f'lock: {path!r} write error: {exc}')
81 try:
82 os.close(fp)
83 except OSError:
84 pass
85 else:
86 return fp
89def _unlock(path, fp):
90 try:
91 fcntl.flock(fp, fcntl.LOCK_UN)
92 except OSError as exc:
93 gws.log.warning(f'lock: {path!r} unlock error: {exc}')
94 try:
95 os.close(fp)
96 except OSError as exc:
97 gws.log.warning(f'lock: {path!r} close error: {exc}')
98 try:
99 os.unlink(path)
100 except OSError as exc:
101 gws.log.warning(f'lock: {path!r} unlink error: {exc}')