Coverage for drivers/lock.py : 74%

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
17"""Serialization for concurrent operations"""
19import os
20import errno
21import flock
22import util
24VERBOSE = True
26# Still just called "running" for backwards compatibility
27LOCK_TYPE_GC_RUNNING = "running"
28LOCK_TYPE_ISCSIADM_RUNNING = "isciadm_running"
30class LockException(util.SMException):
31 pass
34class Lock(object):
35 """Simple file-based lock on a local FS. With shared reader/writer
36 attributes."""
38 BASE_DIR = "/var/lock/sm"
40 INSTANCES = {}
41 BASE_INSTANCES = {}
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
51 if name not in instances:
52 instances[name] = LockImplementation(name, ns)
53 return instances[name]
55 def acquire(self):
56 raise NotImplementedError("Lock methods implemented in LockImplementation")
58 def acquireNoblock(self):
59 raise NotImplementedError("Lock methods implemented in LockImplementation")
61 def release(self):
62 raise NotImplementedError("Lock methods implemented in LockImplementation")
64 def held(self):
65 raise NotImplementedError("Lock methods implemented in LockImplementation")
67 def _mknamespace(ns):
69 if ns is None:
70 return ".nil"
72 assert not ns.startswith(".")
73 assert ns.find(os.path.sep) < 0
74 return ns
75 _mknamespace = staticmethod(_mknamespace)
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 = {}
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]
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)
100 cleanup = staticmethod(cleanup)
102 def cleanupAll(ns=None):
103 ns = Lock._mknamespace(ns)
104 nspath = os.path.join(Lock.BASE_DIR, ns)
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
109 for file in os.listdir(nspath):
110 path = os.path.join(nspath, file)
111 Lock._unlink(path)
113 Lock._rmdir(nspath)
115 cleanupAll = staticmethod(cleanupAll)
116 #
117 # Lock and attribute file management
118 #
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)
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)
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)
150class LockImplementation(object):
152 def __init__(self, name, ns=None):
153 self.lockfile = None
155 self.ns = Lock._mknamespace(ns)
157 assert not name.startswith(".")
158 assert name.find(os.path.sep) < 0
159 self.name = name
161 self.count = 0
163 self._open()
165 def _open(self):
166 """Create and open the lockable attribute base, if it doesn't exist.
167 (But don't lock it yet.)"""
169 # one directory per namespace
170 self.nspath = os.path.join(Lock.BASE_DIR, self.ns)
172 # the lockfile inside that namespace directory per namespace
173 self.lockpath = os.path.join(self.nspath, self.name)
175 number_of_enoent_retries = 10
177 while True:
178 Lock._mkdirs(self.nspath)
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
193 fd = self.lockfile.fileno()
194 self.lock = flock.WriteLock(fd)
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+")
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
212 __del__ = _close
214 def cleanup(self, name, ns=None):
215 Lock.cleanup(name, ns)
217 def cleanupAll(self, ns=None):
218 Lock.cleanupAll(ns)
219 #
220 # Actual Locking
221 #
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
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
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
250 return ret
252 def held(self):
253 """True if @self acquired the lock, False otherwise."""
254 return self.lock.held()
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
261 if self.count > 0:
262 return
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)