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
21 if False:
22 debuglog = log.msg
23 else:
24 debuglog = lambda m: None
25
27 """
28 Class handling claiming and releasing of L{self}, and keeping track of
29 current and waiting owners.
30
31 @note: Ideally, we'd like to maintain FIFO order. The place to do that
32 would be the L{isAvailable()} function. However, this function is
33 called by builds/steps both for the first time, and after waking
34 them up by L{self} from the L{self.waiting} queue. There is
35 currently no way of distinguishing between them.
36 """
37 description = "<BaseLock>"
38
40 self.name = name
41 self.waiting = []
42 self.owners = []
43 self.maxCount = maxCount
44
47
49 """ Return the number of current exclusive and counting owners.
50
51 @return: Tuple (number exclusive owners, number counting owners)
52 """
53 num_excl, num_counting = 0, 0
54 for owner in self.owners:
55 if owner[1].mode == 'exclusive':
56 num_excl = num_excl + 1
57 else:
58 num_counting = num_counting + 1
59
60 assert (num_excl == 1 and num_counting == 0) \
61 or (num_excl == 0 and num_counting <= self.maxCount)
62 return num_excl, num_counting
63
64
66 """ Return a boolean whether the lock is available for claiming """
67 debuglog("%s isAvailable(%s): self.owners=%r"
68 % (self, access, self.owners))
69 num_excl, num_counting = self._getOwnersCount()
70 if access.mode == 'counting':
71
72 return num_excl == 0 and num_counting < self.maxCount
73 else:
74
75 return num_excl == 0 and num_counting == 0
76
77 - def claim(self, owner, access):
87
89 """ Release the lock """
90 assert isinstance(access, LockAccess)
91
92 debuglog("%s release(%s, %s)" % (self, owner, access.mode))
93 entry = (owner, access)
94 assert entry in self.owners
95 self.owners.remove(entry)
96
97
98
99 num_excl, num_counting = self._getOwnersCount()
100 while len(self.waiting) > 0:
101 access, d = self.waiting[0]
102 if access.mode == 'counting':
103 if num_excl > 0 or num_counting == self.maxCount:
104 break
105 else:
106 num_counting = num_counting + 1
107 else:
108
109 if num_excl > 0 or num_counting > 0:
110 break
111 else:
112 num_excl = num_excl + 1
113
114 del self.waiting[0]
115 reactor.callLater(0, d.callback, self)
116
118 """Fire when the lock *might* be available. The caller will need to
119 check with isAvailable() when the deferred fires. This loose form is
120 used to avoid deadlocks. If we were interested in a stronger form,
121 this would be named 'waitUntilAvailable', and the deferred would fire
122 after the lock had been claimed.
123 """
124 debuglog("%s waitUntilAvailable(%s)" % (self, owner))
125 assert isinstance(access, LockAccess)
126 if self.isAvailable(access):
127 return defer.succeed(self)
128 d = defer.Deferred()
129 self.waiting.append((access, d))
130 return d
131
137
139 return (owner, access) in self.owners
140
141
149
152 self.name = lockid.name
153 self.maxCount = lockid.maxCount
154 self.maxCountForSlave = lockid.maxCountForSlave
155 self.description = "<SlaveLock(%s, %s, %s)>" % (self.name,
156 self.maxCount,
157 self.maxCountForSlave)
158 self.locks = {}
159
162
174
175
177 """ I am an object representing a way to access a lock.
178
179 @param lockid: LockId instance that should be accessed.
180 @type lockid: A MasterLock or SlaveLock instance.
181
182 @param mode: Mode of accessing the lock.
183 @type mode: A string, either 'counting' or 'exclusive'.
184 """
185
186 compare_attrs = ['lockid', 'mode']
193
194
196 """ Abstract base class for LockId classes.
197
198 Sets up the 'access()' function for the LockId's available to the user
199 (MasterLock and SlaveLock classes).
200 Derived classes should add
201 - Comparison with the L{util.ComparableMixin} via the L{compare_attrs}
202 class variable.
203 - Link to the actual lock class should be added with the L{lockClass}
204 class variable.
205 """
207 """ Express how the lock should be accessed """
208 assert mode in ['counting', 'exclusive']
209 return LockAccess(self, mode)
210
212 """ For buildbot 0.7.7 compability: When user doesn't specify an access
213 mode, this one is chosen.
214 """
215 return self.access('counting')
216
217
218
219
220
221
222
224 """I am a semaphore that limits the number of simultaneous actions.
225
226 Builds and BuildSteps can declare that they wish to claim me as they run.
227 Only a limited number of such builds or steps will be able to run
228 simultaneously. By default this number is one, but my maxCount parameter
229 can be raised to allow two or three or more operations to happen at the
230 same time.
231
232 Use this to protect a resource that is shared among all builders and all
233 slaves, for example to limit the load on a common SVN repository.
234 """
235
236 compare_attrs = ['name', 'maxCount']
237 lockClass = RealMasterLock
239 self.name = name
240 self.maxCount = maxCount
241
243 """I am a semaphore that limits simultaneous actions on each buildslave.
244
245 Builds and BuildSteps can declare that they wish to claim me as they run.
246 Only a limited number of such builds or steps will be able to run
247 simultaneously on any given buildslave. By default this number is one,
248 but my maxCount parameter can be raised to allow two or three or more
249 operations to happen on a single buildslave at the same time.
250
251 Use this to protect a resource that is shared among all the builds taking
252 place on each slave, for example to limit CPU or memory load on an
253 underpowered machine.
254
255 Each buildslave will get an independent copy of this semaphore. By
256 default each copy will use the same owner count (set with maxCount), but
257 you can provide maxCountForSlave with a dictionary that maps slavename to
258 owner count, to allow some slaves more parallelism than others.
259
260 """
261
262 compare_attrs = ['name', 'maxCount', '_maxCountForSlaveList']
263 lockClass = RealSlaveLock
264 - def __init__(self, name, maxCount=1, maxCountForSlave={}):
265 self.name = name
266 self.maxCount = maxCount
267 self.maxCountForSlave = maxCountForSlave
268
269
270 self._maxCountForSlaveList = self.maxCountForSlave.items()
271 self._maxCountForSlaveList.sort()
272 self._maxCountForSlaveList = tuple(self._maxCountForSlaveList)
273