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 def acquire(self): 

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

54 

55 def acquireNoblock(self): 

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

57 

58 def release(self): 

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

60 

61 def held(self): 

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

63 

64 def _mknamespace(ns): 

65 

66 if ns is None: 

67 return ".nil" 

68 

69 assert not ns.startswith(".") 

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

71 return ns 

72 _mknamespace = staticmethod(_mknamespace) 

73 

74 @staticmethod 

75 def clearAll(): 

76 """ 

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

78 """ 

79 Lock.INSTANCES = {} 

80 Lock.BASE_INSTANCES = {} 

81 

82 def cleanup(name, ns=None): 

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

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

85 if name in Lock.INSTANCES[ns]: 

86 del Lock.INSTANCES[ns][name] 

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

88 del Lock.INSTANCES[ns] 

89 elif name in Lock.BASE_INSTANCES: 

90 del Lock.BASE_INSTANCES[name] 

91 

92 ns = Lock._mknamespace(ns) 

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

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

95 Lock._unlink(path) 

96 

97 cleanup = staticmethod(cleanup) 

98 

99 def cleanupAll(ns=None): 

100 ns = Lock._mknamespace(ns) 

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

102 

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

104 return 

105 

106 for file in os.listdir(nspath): 

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

108 Lock._unlink(path) 

109 

110 Lock._rmdir(nspath) 

111 

112 cleanupAll = staticmethod(cleanupAll) 

113 # 

114 # Lock and attribute file management 

115 # 

116 

117 def _mkdirs(path): 

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

119 if os.path.exists(path): 

120 return 

121 try: 

122 os.makedirs(path) 

123 except OSError as e: 

124 if e.errno != errno.EEXIST: 

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

126 _mkdirs = staticmethod(_mkdirs) 

127 

128 def _unlink(path): 

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

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

131 try: 

132 os.unlink(path) 

133 except Exception as e: 

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

135 _unlink = staticmethod(_unlink) 

136 

137 def _rmdir(path): 

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

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

140 try: 

141 os.rmdir(path) 

142 except Exception as e: 

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

144 _rmdir = staticmethod(_rmdir) 

145 

146 

147class LockImplementation(object): 

148 

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

150 self.lockfile = None 

151 

152 self.ns = Lock._mknamespace(ns) 

153 

154 assert not name.startswith(".") 

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

156 self.name = name 

157 

158 self.count = 0 

159 

160 self._open() 

161 

162 def _open(self): 

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

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

165 

166 # one directory per namespace 

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

168 

169 # the lockfile inside that namespace directory per namespace 

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

171 

172 number_of_enoent_retries = 10 

173 

174 while True: 

175 Lock._mkdirs(self.nspath) 

176 

177 try: 

178 self._open_lockfile() 

179 except IOError as e: 

180 # If another lock within the namespace has already 

181 # cleaned up the namespace by removing the directory, 

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

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

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

185 number_of_enoent_retries -= 1 

186 continue 

187 raise 

188 break 

189 

190 fd = self.lockfile.fileno() 

191 self.lock = flock.WriteLock(fd) 

192 

193 def _open_lockfile(self): 

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

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

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

197 

198 def _close(self): 

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

200 if self.lockfile is not None: 200 ↛ exitline 200 didn't return from function '_close', because the condition on line 200 was never false

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

202 # drop all reference counts 

203 self.count = 0 

204 self.release() 

205 self.lockfile.close() 

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

207 self.lockfile = None 

208 

209 __del__ = _close 

210 

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

212 Lock.cleanup(name, ns) 

213 

214 def cleanupAll(self, ns=None): 

215 Lock.cleanupAll(ns) 

216 # 

217 # Actual Locking 

218 # 

219 

220 def acquire(self): 

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

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

223 and aid debugging.""" 

224 if not self.held(): 

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

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

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

228 self.lock.lock() 

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

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

231 self.count += 1 

232 

233 def acquireNoblock(self): 

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

235 if not self.held(): 

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

237 ret = self.lock.trylock() 

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

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

240 (self.lockpath, ret, exists)) 

241 else: 

242 ret = True 

243 

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

245 self.count += 1 

246 

247 return ret 

248 

249 def held(self): 

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

251 return self.lock.held() 

252 

253 def release(self): 

254 """Release a previously acquired lock.""" 

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

256 self.count -= 1 

257 

258 if self.count > 0: 

259 return 

260 

261 self.lock.unlock() 

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

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