1
2
3 from twisted.python import log
4 from twisted.internet import reactor, defer
5 from buildbot import util
6
7 if False:
8 debuglog = log.msg
9 else:
10 debuglog = lambda m: None
11
13 """
14 Class handling claiming and releasing of L{self}, and keeping track of
15 current and waiting owners.
16
17 @note: Ideally, we'd like to maintain FIFO order. The place to do that
18 would be the L{isAvailable()} function. However, this function is
19 called by builds/steps both for the first time, and after waking
20 them up by L{self} from the L{self.waiting} queue. There is
21 currently no way of distinguishing between them.
22 """
23 description = "<BaseLock>"
24
26 self.name = name
27 self.waiting = []
28 self.owners = []
29 self.maxCount = maxCount
30
33
35 """ Return the number of current exclusive and counting owners.
36
37 @return: Tuple (number exclusive owners, number counting owners)
38 """
39 num_excl, num_counting = 0, 0
40 for owner in self.owners:
41 if owner[1].mode == 'exclusive':
42 num_excl = num_excl + 1
43 else:
44 num_counting = num_counting + 1
45
46 assert (num_excl == 1 and num_counting == 0) \
47 or (num_excl == 0 and num_counting <= self.maxCount)
48 return num_excl, num_counting
49
50
52 """ Return a boolean whether the lock is available for claiming """
53 debuglog("%s isAvailable(%s): self.owners=%r"
54 % (self, access, self.owners))
55 num_excl, num_counting = self._getOwnersCount()
56 if access.mode == 'counting':
57
58 return num_excl == 0 and num_counting < self.maxCount
59 else:
60
61 return num_excl == 0 and num_counting == 0
62
63 - def claim(self, owner, access):
73
75 """ Release the lock """
76 assert isinstance(access, LockAccess)
77
78 debuglog("%s release(%s, %s)" % (self, owner, access.mode))
79 entry = (owner, access)
80 assert entry in self.owners
81 self.owners.remove(entry)
82
83
84
85 num_excl, num_counting = self._getOwnersCount()
86 while len(self.waiting) > 0:
87 access, d = self.waiting[0]
88 if access.mode == 'counting':
89 if num_excl > 0 or num_counting == self.maxCount:
90 break
91 else:
92 num_counting = num_counting + 1
93 else:
94
95 if num_excl > 0 or num_counting > 0:
96 break
97 else:
98 num_excl = num_excl + 1
99
100 del self.waiting[0]
101 reactor.callLater(0, d.callback, self)
102
104 """Fire when the lock *might* be available. The caller will need to
105 check with isAvailable() when the deferred fires. This loose form is
106 used to avoid deadlocks. If we were interested in a stronger form,
107 this would be named 'waitUntilAvailable', and the deferred would fire
108 after the lock had been claimed.
109 """
110 debuglog("%s waitUntilAvailable(%s)" % (self, owner))
111 assert isinstance(access, LockAccess)
112 if self.isAvailable(access):
113 return defer.succeed(self)
114 d = defer.Deferred()
115 self.waiting.append((access, d))
116 return d
117
123
125 return (owner, access) in self.owners
126
127
135
138 self.name = lockid.name
139 self.maxCount = lockid.maxCount
140 self.maxCountForSlave = lockid.maxCountForSlave
141 self.description = "<SlaveLock(%s, %s, %s)>" % (self.name,
142 self.maxCount,
143 self.maxCountForSlave)
144 self.locks = {}
145
148
160
161
163 """ I am an object representing a way to access a lock.
164
165 @param lockid: LockId instance that should be accessed.
166 @type lockid: A MasterLock or SlaveLock instance.
167
168 @param mode: Mode of accessing the lock.
169 @type mode: A string, either 'counting' or 'exclusive'.
170 """
171
172 compare_attrs = ['lockid', 'mode']
179
180
182 """ Abstract base class for LockId classes.
183
184 Sets up the 'access()' function for the LockId's available to the user
185 (MasterLock and SlaveLock classes).
186 Derived classes should add
187 - Comparison with the L{util.ComparableMixin} via the L{compare_attrs}
188 class variable.
189 - Link to the actual lock class should be added with the L{lockClass}
190 class variable.
191 """
193 """ Express how the lock should be accessed """
194 assert mode in ['counting', 'exclusive']
195 return LockAccess(self, mode)
196
198 """ For buildbot 0.7.7 compability: When user doesn't specify an access
199 mode, this one is chosen.
200 """
201 return self.access('counting')
202
203
204
205
206
207
208
210 """I am a semaphore that limits the number of simultaneous actions.
211
212 Builds and BuildSteps can declare that they wish to claim me as they run.
213 Only a limited number of such builds or steps will be able to run
214 simultaneously. By default this number is one, but my maxCount parameter
215 can be raised to allow two or three or more operations to happen at the
216 same time.
217
218 Use this to protect a resource that is shared among all builders and all
219 slaves, for example to limit the load on a common SVN repository.
220 """
221
222 compare_attrs = ['name', 'maxCount']
223 lockClass = RealMasterLock
225 self.name = name
226 self.maxCount = maxCount
227
229 """I am a semaphore that limits simultaneous actions on each buildslave.
230
231 Builds and BuildSteps can declare that they wish to claim me as they run.
232 Only a limited number of such builds or steps will be able to run
233 simultaneously on any given buildslave. By default this number is one,
234 but my maxCount parameter can be raised to allow two or three or more
235 operations to happen on a single buildslave at the same time.
236
237 Use this to protect a resource that is shared among all the builds taking
238 place on each slave, for example to limit CPU or memory load on an
239 underpowered machine.
240
241 Each buildslave will get an independent copy of this semaphore. By
242 default each copy will use the same owner count (set with maxCount), but
243 you can provide maxCountForSlave with a dictionary that maps slavename to
244 owner count, to allow some slaves more parallelism than others.
245
246 """
247
248 compare_attrs = ['name', 'maxCount', '_maxCountForSlaveList']
249 lockClass = RealSlaveLock
250 - def __init__(self, name, maxCount=1, maxCountForSlave={}):
251 self.name = name
252 self.maxCount = maxCount
253 self.maxCountForSlave = maxCountForSlave
254
255
256 self._maxCountForSlaveList = self.maxCountForSlave.items()
257 self._maxCountForSlaveList.sort()
258 self._maxCountForSlaveList = tuple(self._maxCountForSlaveList)
259