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# Still just called "running" for backwards compatibility 

27LOCK_TYPE_GC_RUNNING = "running" 

28LOCK_TYPE_ISCSIADM_RUNNING = "isciadm_running" 

29 

30class LockException(util.SMException): 

31 pass 

32 

33 

34class Lock(object): 

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

36 attributes.""" 

37 

38 BASE_DIR = "/var/lock/sm" 

39 

40 INSTANCES = {} 

41 BASE_INSTANCES = {} 

42 

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

44 if ns: 

45 if ns not in Lock.INSTANCES: 

46 Lock.INSTANCES[ns] = {} 

47 instances = Lock.INSTANCES[ns] 

48 else: 

49 instances = Lock.BASE_INSTANCES 

50 

51 if name not in instances: 

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

53 return instances[name] 

54 

55 def acquire(self): 

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

57 

58 def acquireNoblock(self): 

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

60 

61 def release(self): 

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

63 

64 def held(self): 

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

66 

67 def _mknamespace(ns): 

68 

69 if ns is None: 

70 return ".nil" 

71 

72 assert not ns.startswith(".") 

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

74 return ns 

75 _mknamespace = staticmethod(_mknamespace) 

76 

77 @staticmethod 

78 def clearAll(): 

79 """ 

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

81 """ 

82 Lock.INSTANCES = {} 

83 Lock.BASE_INSTANCES = {} 

84 

85 def cleanup(name, ns=None): 

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

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

88 if name in Lock.INSTANCES[ns]: 

89 del Lock.INSTANCES[ns][name] 

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

91 del Lock.INSTANCES[ns] 

92 elif name in Lock.BASE_INSTANCES: 

93 del Lock.BASE_INSTANCES[name] 

94 

95 ns = Lock._mknamespace(ns) 

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

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

98 Lock._unlink(path) 

99 

100 cleanup = staticmethod(cleanup) 

101 

102 def cleanupAll(ns=None): 

103 ns = Lock._mknamespace(ns) 

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

105 

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

107 return 

108 

109 for file in os.listdir(nspath): 

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

111 Lock._unlink(path) 

112 

113 Lock._rmdir(nspath) 

114 

115 cleanupAll = staticmethod(cleanupAll) 

116 # 

117 # Lock and attribute file management 

118 # 

119 

120 def _mkdirs(path): 

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

122 if os.path.exists(path): 

123 return 

124 try: 

125 os.makedirs(path) 

126 except OSError as e: 

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

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

129 _mkdirs = staticmethod(_mkdirs) 

130 

131 def _unlink(path): 

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

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

134 try: 

135 os.unlink(path) 

136 except Exception as e: 

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

138 _unlink = staticmethod(_unlink) 

139 

140 def _rmdir(path): 

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

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

143 try: 

144 os.rmdir(path) 

145 except Exception as e: 

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

147 _rmdir = staticmethod(_rmdir) 

148 

149 

150class LockImplementation(object): 

151 

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

153 self.lockfile = None 

154 

155 self.ns = Lock._mknamespace(ns) 

156 

157 assert not name.startswith(".") 

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

159 self.name = name 

160 

161 self.count = 0 

162 

163 self._open() 

164 

165 def _open(self): 

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

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

168 

169 # one directory per namespace 

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

171 

172 # the lockfile inside that namespace directory per namespace 

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

174 

175 number_of_enoent_retries = 10 

176 

177 while True: 

178 Lock._mkdirs(self.nspath) 

179 

180 try: 

181 self._open_lockfile() 

182 except IOError as e: 

183 # If another lock within the namespace has already 

184 # cleaned up the namespace by removing the directory, 

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

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

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

188 number_of_enoent_retries -= 1 

189 continue 

190 raise 

191 break 

192 

193 fd = self.lockfile.fileno() 

194 self.lock = flock.WriteLock(fd) 

195 

196 def _open_lockfile(self): 

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

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

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

200 

201 def _close(self): 

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

203 if self.lockfile is not None: 

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

205 # drop all reference counts 

206 self.count = 0 

207 self.release() 

208 self.lockfile.close() 

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

210 self.lockfile = None 

211 

212 __del__ = _close 

213 

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

215 Lock.cleanup(name, ns) 

216 

217 def cleanupAll(self, ns=None): 

218 Lock.cleanupAll(ns) 

219 # 

220 # Actual Locking 

221 # 

222 

223 def acquire(self): 

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

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

226 and aid debugging.""" 

227 if not self.held(): 

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

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

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

231 self.lock.lock() 

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

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

234 self.count += 1 

235 

236 def acquireNoblock(self): 

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

238 if not self.held(): 

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

240 ret = self.lock.trylock() 

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

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

243 (self.lockpath, ret, exists)) 

244 else: 

245 ret = True 

246 

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

248 self.count += 1 

249 

250 return ret 

251 

252 def held(self): 

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

254 return self.lock.held() 

255 

256 def release(self): 

257 """Release a previously acquired lock.""" 

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

259 self.count -= 1 

260 

261 if self.count > 0: 

262 return 

263 

264 self.lock.unlock() 

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

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