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