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 

26import os 

27import fcntl 

28import struct 

29import errno 

30 

31 

32class Flock: 

33 """A C flock struct.""" 

34 

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

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

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

38 

39 FORMAT = "hhqql" 

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

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

42 

43 def fcntl(self, fd, cmd): 

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

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

46 

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

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

49 

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

51 self.__init__( * fields) 

52 

53 FIELDS = {'l_type': 0, 

54 'l_whence': 1, 

55 'l_start': 2, 

56 'l_len': 3, 

57 'l_pid': 4} 

58 

59 def __getattr__(self, name): 

60 idx = self.FIELDS[name] 

61 return self.fields[idx] 

62 

63 def __setattr__(self, name, value): 

64 idx = self.FIELDS.get(name) 

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

66 self.__dict__[name] = value 

67 else: 

68 self.fields[idx] = value 

69 

70 

71class FcntlLockBase: 

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

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

74 type.""" 

75 

76 LOCK_TYPE = None 

77 

78 if __debug__: 

79 ERROR_ISLOCKED = "Attempt to acquire lock held." 

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

81 

82 def __init__(self, fd): 

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

84 self.fd = fd 

85 # 

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

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

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

89 # 

90 self._held = False 

91 

92 def lock(self): 

93 """Blocking lock aquisition.""" 

94 assert not self._held, self.ERROR_ISLOCKED 

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

96 self._held = True 

97 

98 def trylock(self): 

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

100 otherwise.""" 

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

102 return False 

103 try: 

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

105 except IOError as e: 

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

107 return False 

108 raise 

109 self._held = True 

110 return True 

111 

112 def held(self): 

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

114 return self._held 

115 

116 def unlock(self): 

117 """Release a previously acquired lock.""" 

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

119 self._held = False 

120 

121 def test(self): 

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

123 is not held.""" 

124 if self._held: 

125 return os.getpid() 

126 flock = Flock(self.LOCK_TYPE) 

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

128 if flock.l_type == fcntl.F_UNLCK: 

129 return -1 

130 return flock.l_pid 

131 

132 

133class WriteLock(FcntlLockBase): 

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

135 LOCK_TYPE = fcntl.F_WRLCK 

136 

137 

138class ReadLock(FcntlLockBase): 

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

140 LOCK_TYPE = fcntl.F_RDLCK