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

1"""Utilities for locking""" 

2 

3import gws 

4 

5import os 

6import fcntl 

7import time 

8 

9 

10class SoftFileLock: 

11 """Soft file-based locking. 

12 

13 Attempt to lock a file and yield the success status. 

14 """ 

15 

16 def __init__(self, path: str): 

17 self.path = path 

18 self.fp = None 

19 

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 

26 

27 def __exit__(self, exc_type, exc_val, exc_tb): 

28 if self.fp: 

29 _unlock(self.path, self.fp) 

30 self.fp = None 

31 

32 

33class HardFileLock: 

34 """Hard file-based locking. 

35 

36 Keep attempting to lock a file until the timeout expires. 

37 """ 

38 

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 

44 

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}') 

52 

53 def __exit__(self, exc_type, exc_val, exc_tb): 

54 if self.fp: 

55 _unlock(self.path, self.fp) 

56 self.fp = None 

57 

58 

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 

65 

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 

76 

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 

87 

88 

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}')