1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 from twisted.python import log
18 from twisted.internet import defer
19 from buildbot import util
20 from buildbot.util import subscription
21 from buildbot.util.eventual import eventually
22
23 if False:
24 debuglog = log.msg
25 else:
26 debuglog = lambda m: None
27
29 """
30 Class handling claiming and releasing of L{self}, and keeping track of
31 current and waiting owners.
32
33 We maintain the wait queue in FIFO order, and ensure that counting waiters
34 in the queue behind exclusive waiters cannot acquire the lock. This ensures
35 that exclusive waiters are not starved.
36 """
37 description = "<BaseLock>"
38
49
52
54 """ Return the number of current exclusive and counting owners.
55
56 @return: Tuple (number exclusive owners, number counting owners)
57 """
58 num_excl, num_counting = 0, 0
59 for owner in self.owners:
60 if owner[1].mode == 'exclusive':
61 num_excl = num_excl + 1
62 else:
63 num_counting = num_counting + 1
64
65 assert (num_excl == 1 and num_counting == 0) \
66 or (num_excl == 0 and num_counting <= self.maxCount)
67 return num_excl, num_counting
68
69
71 """ Return a boolean whether the lock is available for claiming """
72 debuglog("%s isAvailable(%s, %s): self.owners=%r"
73 % (self, requester, access, self.owners))
74 num_excl, num_counting = self._getOwnersCount()
75
76
77 for idx, waiter in enumerate(self.waiting):
78 if waiter[0] == requester:
79 w_index = idx
80 break
81 else:
82 w_index = len(self.waiting)
83 ahead = self.waiting[:w_index]
84
85 if access.mode == 'counting':
86
87 return num_excl == 0 and num_counting + len(ahead) < self.maxCount \
88 and all([w[1].mode == 'counting' for w in ahead])
89 else:
90
91 return num_excl == 0 and num_counting == 0 and len(ahead) == 0
92
93 - def claim(self, owner, access):
94 """ Claim the lock (lock must be available) """
95 debuglog("%s claim(%s, %s)" % (self, owner, access.mode))
96 assert owner is not None
97 assert self.isAvailable(owner, access), "ask for isAvailable() first"
98
99 assert isinstance(access, LockAccess)
100 assert access.mode in ['counting', 'exclusive']
101 self.waiting = [w for w in self.waiting if w[0] != owner]
102 self.owners.append((owner, access))
103 debuglog(" %s is claimed '%s'" % (self, access.mode))
104
106 """Schedule C{callback} to be invoked every time this lock is
107 released. Returns a L{Subscription}."""
108 return self.release_subs.subscribe(callback)
109
111 """ Release the lock """
112 assert isinstance(access, LockAccess)
113
114 debuglog("%s release(%s, %s)" % (self, owner, access.mode))
115 entry = (owner, access)
116 if not entry in self.owners:
117 debuglog("%s already released" % self)
118 return
119 self.owners.remove(entry)
120
121
122
123 num_excl, num_counting = self._getOwnersCount()
124 for i, (w_owner, w_access, d) in enumerate(self.waiting):
125 if w_access.mode == 'counting':
126 if num_excl > 0 or num_counting == self.maxCount:
127 break
128 else:
129 num_counting = num_counting + 1
130 else:
131
132 if num_excl > 0 or num_counting > 0:
133 break
134 else:
135 num_excl = num_excl + 1
136
137
138
139 if d:
140 self.waiting[i] = (w_owner, w_access, None)
141 eventually(d.callback, self)
142
143
144 self.release_subs.deliver()
145
147 """Fire when the lock *might* be available. The caller will need to
148 check with isAvailable() when the deferred fires. This loose form is
149 used to avoid deadlocks. If we were interested in a stronger form,
150 this would be named 'waitUntilAvailable', and the deferred would fire
151 after the lock had been claimed.
152 """
153 debuglog("%s waitUntilAvailable(%s)" % (self, owner))
154 assert isinstance(access, LockAccess)
155 if self.isAvailable(owner, access):
156 return defer.succeed(self)
157 d = defer.Deferred()
158
159
160 w = [i for i, w in enumerate(self.waiting) if w[0] == owner]
161 if w:
162 self.waiting[w[0]] = (owner, access, d)
163 else:
164 self.waiting.append((owner, access, d))
165 return d
166
168 debuglog("%s stopWaitingUntilAvailable(%s)" % (self, owner))
169 assert isinstance(access, LockAccess)
170 assert (owner, access, d) in self.waiting
171 self.waiting = [w for w in self.waiting if w[0] != owner]
172
174 return (owner, access) in self.owners
175
176
184
187 self.name = lockid.name
188 self.maxCount = lockid.maxCount
189 self.maxCountForSlave = lockid.maxCountForSlave
190 self.description = "<SlaveLock(%s, %s, %s)>" % (self.name,
191 self.maxCount,
192 self.maxCountForSlave)
193 self.locks = {}
194
197
209
210
212 """ I am an object representing a way to access a lock.
213
214 @param lockid: LockId instance that should be accessed.
215 @type lockid: A MasterLock or SlaveLock instance.
216
217 @param mode: Mode of accessing the lock.
218 @type mode: A string, either 'counting' or 'exclusive'.
219 """
220
221 compare_attrs = ['lockid', 'mode']
222 - def __init__(self, lockid, mode, _skipChecks=False):
223 self.lockid = lockid
224 self.mode = mode
225
226 if not _skipChecks:
227
228
229 assert isinstance(lockid, (MasterLock, SlaveLock))
230 assert mode in ['counting', 'exclusive']
231
232
234 """ Abstract base class for LockId classes.
235
236 Sets up the 'access()' function for the LockId's available to the user
237 (MasterLock and SlaveLock classes).
238 Derived classes should add
239 - Comparison with the L{util.ComparableMixin} via the L{compare_attrs}
240 class variable.
241 - Link to the actual lock class should be added with the L{lockClass}
242 class variable.
243 """
245 """ Express how the lock should be accessed """
246 assert mode in ['counting', 'exclusive']
247 return LockAccess(self, mode)
248
250 """ For buildbot 0.7.7 compability: When user doesn't specify an access
251 mode, this one is chosen.
252 """
253 return self.access('counting')
254
255
256
257
258
259
260
262 """I am a semaphore that limits the number of simultaneous actions.
263
264 Builds and BuildSteps can declare that they wish to claim me as they run.
265 Only a limited number of such builds or steps will be able to run
266 simultaneously. By default this number is one, but my maxCount parameter
267 can be raised to allow two or three or more operations to happen at the
268 same time.
269
270 Use this to protect a resource that is shared among all builders and all
271 slaves, for example to limit the load on a common SVN repository.
272 """
273
274 compare_attrs = ['name', 'maxCount']
275 lockClass = RealMasterLock
277 self.name = name
278 self.maxCount = maxCount
279
281 """I am a semaphore that limits simultaneous actions on each buildslave.
282
283 Builds and BuildSteps can declare that they wish to claim me as they run.
284 Only a limited number of such builds or steps will be able to run
285 simultaneously on any given buildslave. By default this number is one,
286 but my maxCount parameter can be raised to allow two or three or more
287 operations to happen on a single buildslave at the same time.
288
289 Use this to protect a resource that is shared among all the builds taking
290 place on each slave, for example to limit CPU or memory load on an
291 underpowered machine.
292
293 Each buildslave will get an independent copy of this semaphore. By
294 default each copy will use the same owner count (set with maxCount), but
295 you can provide maxCountForSlave with a dictionary that maps slavename to
296 owner count, to allow some slaves more parallelism than others.
297
298 """
299
300 compare_attrs = ['name', 'maxCount', '_maxCountForSlaveList']
301 lockClass = RealSlaveLock
302 - def __init__(self, name, maxCount=1, maxCountForSlave={}):
303 self.name = name
304 self.maxCount = maxCount
305 self.maxCountForSlave = maxCountForSlave
306
307
308 self._maxCountForSlaveList = self.maxCountForSlave.items()
309 self._maxCountForSlaveList.sort()
310 self._maxCountForSlaveList = tuple(self._maxCountForSlaveList)
311