Coverage for drivers/lock.py : 71%

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
27class LockException(util.SMException):
28 pass
31class Lock(object):
32 """Simple file-based lock on a local FS. With shared reader/writer
33 attributes."""
35 BASE_DIR = "/var/lock/sm"
37 INSTANCES = {}
38 BASE_INSTANCES = {}
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
48 if name not in instances:
49 instances[name] = LockImplementation(name, ns)
50 return instances[name]
52 def acquire(self):
53 raise NotImplementedError("Lock methods implemented in LockImplementation")
55 def acquireNoblock(self):
56 raise NotImplementedError("Lock methods implemented in LockImplementation")
58 def release(self):
59 raise NotImplementedError("Lock methods implemented in LockImplementation")
61 def held(self):
62 raise NotImplementedError("Lock methods implemented in LockImplementation")
64 def _mknamespace(ns):
66 if ns is None:
67 return ".nil"
69 assert not ns.startswith(".")
70 assert ns.find(os.path.sep) < 0
71 return ns
72 _mknamespace = staticmethod(_mknamespace)
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 = {}
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]
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)
97 cleanup = staticmethod(cleanup)
99 def cleanupAll(ns=None):
100 ns = Lock._mknamespace(ns)
101 nspath = os.path.join(Lock.BASE_DIR, ns)
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
106 for file in os.listdir(nspath):
107 path = os.path.join(nspath, file)
108 Lock._unlink(path)
110 Lock._rmdir(nspath)
112 cleanupAll = staticmethod(cleanupAll)
113 #
114 # Lock and attribute file management
115 #
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)
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)
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)
147class LockImplementation(object):
149 def __init__(self, name, ns=None):
150 self.lockfile = None
152 self.ns = Lock._mknamespace(ns)
154 assert not name.startswith(".")
155 assert name.find(os.path.sep) < 0
156 self.name = name
158 self.count = 0
160 self._open()
162 def _open(self):
163 """Create and open the lockable attribute base, if it doesn't exist.
164 (But don't lock it yet.)"""
166 # one directory per namespace
167 self.nspath = os.path.join(Lock.BASE_DIR, self.ns)
169 # the lockfile inside that namespace directory per namespace
170 self.lockpath = os.path.join(self.nspath, self.name)
172 number_of_enoent_retries = 10
174 while True:
175 Lock._mkdirs(self.nspath)
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
190 fd = self.lockfile.fileno()
191 self.lock = flock.WriteLock(fd)
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+")
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
209 __del__ = _close
211 def cleanup(self, name, ns=None):
212 Lock.cleanup(name, ns)
214 def cleanupAll(self, ns=None):
215 Lock.cleanupAll(ns)
216 #
217 # Actual Locking
218 #
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
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
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
247 return ret
249 def held(self):
250 """True if @self acquired the lock, False otherwise."""
251 return self.lock.held()
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
258 if self.count > 0:
259 return
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)