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 @note: Ideally, we'd like to maintain FIFO order. The place to do that
33 would be the L{isAvailable()} function. However, this function is
34 called by builds/steps both for the first time, and after waking
35 them up by L{self} from the L{self.waiting} queue. There is
36 currently no way of distinguishing between them.
37 """
38 description = "<BaseLock>"
39
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): self.owners=%r"
73 % (self, access, self.owners))
74 num_excl, num_counting = self._getOwnersCount()
75 if access.mode == 'counting':
76
77 return num_excl == 0 and num_counting < self.maxCount
78 else:
79
80 return num_excl == 0 and num_counting == 0
81
82 - def claim(self, owner, access):
92
94 """Schedule C{callback} to be invoked every time this lock is
95 released. Returns a L{Subscription}."""
96 return self.release_subs.subscribe(callback)
97
99 """ Release the lock """
100 assert isinstance(access, LockAccess)
101
102 debuglog("%s release(%s, %s)" % (self, owner, access.mode))
103 entry = (owner, access)
104 assert entry in self.owners
105 self.owners.remove(entry)
106
107
108
109 num_excl, num_counting = self._getOwnersCount()
110 while len(self.waiting) > 0:
111 access, d = self.waiting[0]
112 if access.mode == 'counting':
113 if num_excl > 0 or num_counting == self.maxCount:
114 break
115 else:
116 num_counting = num_counting + 1
117 else:
118
119 if num_excl > 0 or num_counting > 0:
120 break
121 else:
122 num_excl = num_excl + 1
123
124 del self.waiting[0]
125 reactor.callLater(0, d.callback, self)
126
127
128 self.release_subs.deliver()
129
131 """Fire when the lock *might* be available. The caller will need to
132 check with isAvailable() when the deferred fires. This loose form is
133 used to avoid deadlocks. If we were interested in a stronger form,
134 this would be named 'waitUntilAvailable', and the deferred would fire
135 after the lock had been claimed.
136 """
137 debuglog("%s waitUntilAvailable(%s)" % (self, owner))
138 assert isinstance(access, LockAccess)
139 if self.isAvailable(access):
140 return defer.succeed(self)
141 d = defer.Deferred()
142 self.waiting.append((access, d))
143 return d
144
150
152 return (owner, access) in self.owners
153
154
162
165 self.name = lockid.name
166 self.maxCount = lockid.maxCount
167 self.maxCountForSlave = lockid.maxCountForSlave
168 self.description = "<SlaveLock(%s, %s, %s)>" % (self.name,
169 self.maxCount,
170 self.maxCountForSlave)
171 self.locks = {}
172
175
187
188
190 """ I am an object representing a way to access a lock.
191
192 @param lockid: LockId instance that should be accessed.
193 @type lockid: A MasterLock or SlaveLock instance.
194
195 @param mode: Mode of accessing the lock.
196 @type mode: A string, either 'counting' or 'exclusive'.
197 """
198
199 compare_attrs = ['lockid', 'mode']
206
207
209 """ Abstract base class for LockId classes.
210
211 Sets up the 'access()' function for the LockId's available to the user
212 (MasterLock and SlaveLock classes).
213 Derived classes should add
214 - Comparison with the L{util.ComparableMixin} via the L{compare_attrs}
215 class variable.
216 - Link to the actual lock class should be added with the L{lockClass}
217 class variable.
218 """
220 """ Express how the lock should be accessed """
221 assert mode in ['counting', 'exclusive']
222 return LockAccess(self, mode)
223
225 """ For buildbot 0.7.7 compability: When user doesn't specify an access
226 mode, this one is chosen.
227 """
228 return self.access('counting')
229
230
231
232
233
234
235
237 """I am a semaphore that limits the number of simultaneous actions.
238
239 Builds and BuildSteps can declare that they wish to claim me as they run.
240 Only a limited number of such builds or steps will be able to run
241 simultaneously. By default this number is one, but my maxCount parameter
242 can be raised to allow two or three or more operations to happen at the
243 same time.
244
245 Use this to protect a resource that is shared among all builders and all
246 slaves, for example to limit the load on a common SVN repository.
247 """
248
249 compare_attrs = ['name', 'maxCount']
250 lockClass = RealMasterLock
252 self.name = name
253 self.maxCount = maxCount
254
256 """I am a semaphore that limits simultaneous actions on each buildslave.
257
258 Builds and BuildSteps can declare that they wish to claim me as they run.
259 Only a limited number of such builds or steps will be able to run
260 simultaneously on any given buildslave. By default this number is one,
261 but my maxCount parameter can be raised to allow two or three or more
262 operations to happen on a single buildslave at the same time.
263
264 Use this to protect a resource that is shared among all the builds taking
265 place on each slave, for example to limit CPU or memory load on an
266 underpowered machine.
267
268 Each buildslave will get an independent copy of this semaphore. By
269 default each copy will use the same owner count (set with maxCount), but
270 you can provide maxCountForSlave with a dictionary that maps slavename to
271 owner count, to allow some slaves more parallelism than others.
272
273 """
274
275 compare_attrs = ['name', 'maxCount', '_maxCountForSlaveList']
276 lockClass = RealSlaveLock
277 - def __init__(self, name, maxCount=1, maxCountForSlave={}):
278 self.name = name
279 self.maxCount = maxCount
280 self.maxCountForSlave = maxCountForSlave
281
282
283 self._maxCountForSlaveList = self.maxCountForSlave.items()
284 self._maxCountForSlaveList.sort()
285 self._maxCountForSlaveList = tuple(self._maxCountForSlaveList)
286