Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# 

2# Copyright (C) Citrix Systems Inc. 

3# 

4# This program is free software; you can redistribute it and/or modify 

5# it under the terms of the GNU Lesser General Public License as published 

6# by the Free Software Foundation; version 2.1 only. 

7# 

8# This program is distributed in the hope that it will be useful, 

9# but WITHOUT ANY WARRANTY; without even the implied warranty of 

10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

11# GNU Lesser General Public License for more details. 

12# 

13# You should have received a copy of the GNU Lesser General Public License 

14# along with this program; if not, write to the Free Software Foundation, Inc., 

15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

16# 

17 

18""" 

19Fcntl-based Advisory Locking with a proper .trylock() 

20 

21Python's fcntl module is not good at locking. In particular, proper 

22testing and trying of locks isn't well supported. Looks as if we've 

23got to grow our own. 

24""" 

25 

26from sm_typing import ClassVar, override 

27 

28import os 

29import fcntl 

30import struct 

31import errno 

32 

33 

34class Flock: 

35 """A C flock struct.""" 

36 

37 def __init__(self, l_type, l_whence=0, l_start=0, l_len=0, l_pid=0): 

38 """See fcntl(2) for field details.""" 

39 self.fields = [l_type, l_whence, l_start, l_len, l_pid] 

40 

41 FORMAT = "hhqql" 

42 # struct flock(2) format, tested with python2.4/i686 and 

43 # python2.5/x86_64. http://docs.python.org/lib/posix-large-files.html 

44 

45 def fcntl(self, fd, cmd): 

46 """Issues a system fcntl(fd, cmd, self). Updates self with what was 

47 returned by the kernel. Otherwise raises IOError(errno).""" 

48 

49 st = struct.pack(self.FORMAT, * self.fields) 

50 st = fcntl.fcntl(fd, cmd, st) 

51 

52 fields = struct.unpack(self.FORMAT, st) 

53 self.__init__( * fields) 

54 

55 FIELDS = {'l_type': 0, 

56 'l_whence': 1, 

57 'l_start': 2, 

58 'l_len': 3, 

59 'l_pid': 4} 

60 

61 def __getattr__(self, name): 

62 idx = self.FIELDS[name] 

63 return self.fields[idx] 

64 

65 @override 

66 def __setattr__(self, name, value) -> None: 

67 idx = self.FIELDS.get(name) 

68 if idx is None: 68 ↛ 71line 68 didn't jump to line 71, because the condition on line 68 was never false

69 self.__dict__[name] = value 

70 else: 

71 self.fields[idx] = value 

72 

73 

74class FcntlLockBase: 

75 """Abstract base class for either reader or writer locks. A respective 

76 definition of LOCK_TYPE (fcntl.{F_RDLCK|F_WRLCK}) determines the 

77 type.""" 

78 

79 LOCK_TYPE: ClassVar[int] 

80 

81 if __debug__: 

82 ERROR_ISLOCKED = "Attempt to acquire lock held." 

83 ERROR_NOTLOCKED = "Attempt to unlock lock not held." 

84 

85 def __init__(self, fd): 

86 """Creates a new, unheld lock.""" 

87 self.fd = fd 

88 # 

89 # Subtle: fcntl(2) permits re-locking it as often as you want 

90 # once you hold it. This is slightly counterintuitive and we 

91 # want clean code, so we add one bit of our own bookkeeping. 

92 # 

93 self._held = False 

94 

95 def lock(self): 

96 """Blocking lock aquisition.""" 

97 assert not self._held, self.ERROR_ISLOCKED 

98 Flock(self.LOCK_TYPE).fcntl(self.fd, fcntl.F_SETLKW) 

99 self._held = True 

100 

101 def trylock(self): 

102 """Non-blocking lock aquisition. Returns True on success, False 

103 otherwise.""" 

104 if self._held: 104 ↛ 105line 104 didn't jump to line 105, because the condition on line 104 was never true

105 return False 

106 try: 

107 Flock(self.LOCK_TYPE).fcntl(self.fd, fcntl.F_SETLK) 

108 except IOError as e: 

109 if e.errno in [errno.EACCES, errno.EAGAIN]: 

110 return False 

111 raise 

112 self._held = True 

113 return True 

114 

115 def held(self): 

116 """Returns True if @self holds the lock, False otherwise.""" 

117 return self._held 

118 

119 def unlock(self): 

120 """Release a previously acquired lock.""" 

121 Flock(fcntl.F_UNLCK).fcntl(self.fd, fcntl.F_SETLK) 

122 self._held = False 

123 

124 def test(self): 

125 """Returns the PID of the process holding the lock or -1 if the lock 

126 is not held.""" 

127 if self._held: 

128 return os.getpid() 

129 flock = Flock(self.LOCK_TYPE) 

130 flock.fcntl(self.fd, fcntl.F_GETLK) 

131 if flock.l_type == fcntl.F_UNLCK: 

132 return -1 

133 return flock.l_pid 

134 

135 

136class WriteLock(FcntlLockBase): 

137 """A simple global writer (i.e. exclusive) lock.""" 

138 LOCK_TYPE = fcntl.F_WRLCK 

139 

140 

141class ReadLock(FcntlLockBase): 

142 """A simple global reader (i.e. shared) lock.""" 

143 LOCK_TYPE = fcntl.F_RDLCK