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"""Serialization for concurrent operations""" 

18 

19import os 

20import errno 

21import flock 

22import util 

23 

24VERBOSE = True 

25 

26 

27class LockException(util.SMException): 

28 pass 

29 

30 

31class Lock(object): 

32 """Simple file-based lock on a local FS. With shared reader/writer 

33 attributes.""" 

34 

35 BASE_DIR = "/var/lock/sm" 

36 

37 INSTANCES = {} 

38 BASE_INSTANCES = {} 

39 

40 def __new__(cls, name, ns=None, *args, **kwargs): 

41 if ns: 

42 if ns not in Lock.INSTANCES: 

43 Lock.INSTANCES[ns] = {} 

44 instances = Lock.INSTANCES[ns] 

45 else: 

46 instances = Lock.BASE_INSTANCES 

47 

48 if name not in instances: 

49 instances[name] = LockImplementation(name, ns) 

50 return instances[name] 

51 

52 # These are required to pacify pylint as it doesn't understand the __new__ 

53 def acquire(self): 

54 raise NotImplementedError("Lock methods implemented in LockImplementation") 

55 

56 def acquireNoblock(self): 

57 raise NotImplementedError("Lock methods implemented in LockImplementation") 

58 

59 def release(self): 

60 raise NotImplementedError("Lock methods implemented in LockImplementation") 

61 

62 def held(self): 

63 raise NotImplementedError("Lock methods implemented in LockImplementation") 

64 

65 def _mknamespace(ns): 

66 

67 if ns is None: 

68 return ".nil" 

69 

70 assert not ns.startswith(".") 

71 assert ns.find(os.path.sep) < 0 

72 return ns 

73 _mknamespace = staticmethod(_mknamespace) 

74 

75 @staticmethod 

76 def clearAll(): 

77 """ 

78 Drop all lock instances, to be used when forking, but not execing 

79 """ 

80 Lock.INSTANCES = {} 

81 Lock.BASE_INSTANCES = {} 

82 

83 def cleanup(name, ns=None): 

84 if ns: 84 ↛ 90line 84 didn't jump to line 90, because the condition on line 84 was never false

85 if ns in Lock.INSTANCES: 85 ↛ 86line 85 didn't jump to line 86, because the condition on line 85 was never true

86 if name in Lock.INSTANCES[ns]: 

87 del Lock.INSTANCES[ns][name] 

88 if len(Lock.INSTANCES[ns]) == 0: 

89 del Lock.INSTANCES[ns] 

90 elif name in Lock.BASE_INSTANCES: 

91 del Lock.BASE_INSTANCES[name] 

92 

93 ns = Lock._mknamespace(ns) 

94 path = os.path.join(Lock.BASE_DIR, ns, name) 

95 if os.path.exists(path): 95 ↛ 96line 95 didn't jump to line 96, because the condition on line 95 was never true

96 Lock._unlink(path) 

97 

98 cleanup = staticmethod(cleanup) 

99 

100 def cleanupAll(ns=None): 

101 ns = Lock._mknamespace(ns) 

102 nspath = os.path.join(Lock.BASE_DIR, ns) 

103 

104 if not os.path.exists(nspath): 104 ↛ 107line 104 didn't jump to line 107, because the condition on line 104 was never false

105 return 

106 

107 for file in os.listdir(nspath): 

108 path = os.path.join(nspath, file) 

109 Lock._unlink(path) 

110 

111 Lock._rmdir(nspath) 

112 

113 cleanupAll = staticmethod(cleanupAll) 

114 # 

115 # Lock and attribute file management 

116 # 

117 

118 def _mkdirs(path): 

119 """Concurrent makedirs() catching EEXIST.""" 

120 if os.path.exists(path): 

121 return 

122 try: 

123 os.makedirs(path) 

124 except OSError as e: 

125 if e.errno != errno.EEXIST: 125 ↛ exitline 125 didn't return from function '_mkdirs', because the condition on line 125 was never false

126 raise LockException("Failed to makedirs(%s)" % path) 

127 _mkdirs = staticmethod(_mkdirs) 

128 

129 def _unlink(path): 

130 """Non-raising unlink().""" 

131 util.SMlog("lock: unlinking lock file %s" % path) 

132 try: 

133 os.unlink(path) 

134 except Exception as e: 

135 util.SMlog("Failed to unlink(%s): %s" % (path, e)) 

136 _unlink = staticmethod(_unlink) 

137 

138 def _rmdir(path): 

139 """Non-raising rmdir().""" 

140 util.SMlog("lock: removing lock dir %s" % path) 

141 try: 

142 os.rmdir(path) 

143 except Exception as e: 

144 util.SMlog("Failed to rmdir(%s): %s" % (path, e)) 

145 _rmdir = staticmethod(_rmdir) 

146 

147 

148class LockImplementation(object): 

149 

150 def __init__(self, name, ns=None): 

151 self.lockfile = None 

152 

153 self.ns = Lock._mknamespace(ns) 

154 

155 assert not name.startswith(".") 

156 assert name.find(os.path.sep) < 0 

157 self.name = name 

158 

159 self.count = 0 

160 

161 self._open() 

162 

163 def _open(self): 

164 """Create and open the lockable attribute base, if it doesn't exist. 

165 (But don't lock it yet.)""" 

166 

167 # one directory per namespace 

168 self.nspath = os.path.join(Lock.BASE_DIR, self.ns) 

169 

170 # the lockfile inside that namespace directory per namespace 

171 self.lockpath = os.path.join(self.nspath, self.name) 

172 

173 number_of_enoent_retries = 10 

174 

175 while True: 

176 Lock._mkdirs(self.nspath) 

177 

178 try: 

179 self._open_lockfile() 

180 except IOError as e: 

181 # If another lock within the namespace has already 

182 # cleaned up the namespace by removing the directory, 

183 # _open_lockfile raises an ENOENT, in this case we retry. 

184 if e.errno == errno.ENOENT: 184 ↛ 188line 184 didn't jump to line 188, because the condition on line 184 was never false

185 if number_of_enoent_retries > 0: 185 ↛ 188line 185 didn't jump to line 188, because the condition on line 185 was never false

186 number_of_enoent_retries -= 1 

187 continue 

188 raise 

189 break 

190 

191 fd = self.lockfile.fileno() 

192 self.lock = flock.WriteLock(fd) 

193 

194 def _open_lockfile(self): 

195 """Provide a seam, so extreme situations could be tested""" 

196 util.SMlog("lock: opening lock file %s" % self.lockpath) 

197 self.lockfile = open(self.lockpath, "w+") 

198 

199 def _close(self): 

200 """Close the lock, which implies releasing the lock.""" 

201 if self.lockfile is not None: 

202 if self.held(): 202 ↛ 204line 202 didn't jump to line 204, because the condition on line 202 was never true

203 # drop all reference counts 

204 self.count = 0 

205 self.release() 

206 self.lockfile.close() 

207 util.SMlog("lock: closed %s" % self.lockpath) 

208 self.lockfile = None 

209 

210 __del__ = _close 

211 

212 def cleanup(self, name, ns=None): 

213 Lock.cleanup(name, ns) 

214 

215 def cleanupAll(self, ns=None): 

216 Lock.cleanupAll(ns) 

217 # 

218 # Actual Locking 

219 # 

220 

221 def acquire(self): 

222 """Blocking lock aquisition, with warnings. We don't expect to lock a 

223 lot. If so, not to collide. Coarse log statements should be ok 

224 and aid debugging.""" 

225 if not self.held(): 

226 if not self.lock.trylock(): 226 ↛ 227line 226 didn't jump to line 227, because the condition on line 226 was never true

227 util.SMlog("Failed to lock %s on first attempt, " % self.lockpath 

228 + "blocked by PID %d" % self.lock.test()) 

229 self.lock.lock() 

230 if VERBOSE: 230 ↛ 232line 230 didn't jump to line 232, because the condition on line 230 was never false

231 util.SMlog("lock: acquired %s" % self.lockpath) 

232 self.count += 1 

233 

234 def acquireNoblock(self): 

235 """Acquire lock if possible, or return false if lock already held""" 

236 if not self.held(): 

237 exists = os.path.exists(self.lockpath) 

238 ret = self.lock.trylock() 

239 if VERBOSE: 239 ↛ 245line 239 didn't jump to line 245, because the condition on line 239 was never false

240 util.SMlog("lock: tried lock %s, acquired: %s (exists: %s)" % \ 

241 (self.lockpath, ret, exists)) 

242 else: 

243 ret = True 

244 

245 if ret: 245 ↛ 248line 245 didn't jump to line 248, because the condition on line 245 was never false

246 self.count += 1 

247 

248 return ret 

249 

250 def held(self): 

251 """True if @self acquired the lock, False otherwise.""" 

252 return self.lock.held() 

253 

254 def release(self): 

255 """Release a previously acquired lock.""" 

256 if self.count >= 1: 256 ↛ 259line 256 didn't jump to line 259, because the condition on line 256 was never false

257 self.count -= 1 

258 

259 if self.count > 0: 

260 return 

261 

262 self.lock.unlock() 

263 if VERBOSE: 263 ↛ exitline 263 didn't return from function 'release', because the condition on line 263 was never false

264 util.SMlog("lock: released %s" % self.lockpath)