blob: 1324eefb5ff46024622eab4375d0bb7c72531e35 [file] [log] [blame]
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07001"""Synchronization primitives."""
2
Yury Selivanov6370f342017-12-10 18:36:12 -05003__all__ = ('Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore')
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07004
5import collections
Andrew Svetlov68b34a72019-05-16 17:52:10 +03006import types
Andrew Svetlov28d8d142017-12-09 20:00:05 +02007import warnings
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07008
9from . import events
10from . import futures
Andrew Svetlov0baa72f2018-09-11 10:13:04 -070011from . import exceptions
Andrew Svetlov68b34a72019-05-16 17:52:10 +030012from .import coroutines
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070013
14
Guido van Rossumab3c8892014-01-25 16:51:57 -080015class _ContextManager:
16 """Context manager.
17
18 This enables the following idiom for acquiring and releasing a
19 lock around a block:
20
21 with (yield from lock):
22 <block>
23
24 while failing loudly when accidentally using:
25
26 with lock:
27 <block>
Andrew Svetlov88743422017-12-11 17:35:49 +020028
29 Deprecated, use 'async with' statement:
30 async with lock:
31 <block>
Guido van Rossumab3c8892014-01-25 16:51:57 -080032 """
33
34 def __init__(self, lock):
35 self._lock = lock
36
37 def __enter__(self):
38 # We have no use for the "as ..." clause in the with
39 # statement for locks.
40 return None
41
42 def __exit__(self, *args):
43 try:
44 self._lock.release()
45 finally:
46 self._lock = None # Crudely prevent reuse.
47
48
Yury Selivanovd08c3632015-05-13 15:15:56 -040049class _ContextManagerMixin:
50 def __enter__(self):
51 raise RuntimeError(
52 '"yield from" should be used as context manager expression')
53
54 def __exit__(self, *args):
55 # This must exist because __enter__ exists, even though that
56 # always raises; that's how the with-statement works.
57 pass
58
Andrew Svetlov68b34a72019-05-16 17:52:10 +030059 @types.coroutine
Yury Selivanovd08c3632015-05-13 15:15:56 -040060 def __iter__(self):
61 # This is not a coroutine. It is meant to enable the idiom:
62 #
63 # with (yield from lock):
64 # <block>
65 #
66 # as an alternative to:
67 #
68 # yield from lock.acquire()
69 # try:
70 # <block>
71 # finally:
72 # lock.release()
Andrew Svetlov88743422017-12-11 17:35:49 +020073 # Deprecated, use 'async with' statement:
74 # async with lock:
75 # <block>
Andrew Svetlov28d8d142017-12-09 20:00:05 +020076 warnings.warn("'with (yield from lock)' is deprecated "
77 "use 'async with lock' instead",
78 DeprecationWarning, stacklevel=2)
Yury Selivanovd08c3632015-05-13 15:15:56 -040079 yield from self.acquire()
80 return _ContextManager(self)
81
Andrew Svetlov68b34a72019-05-16 17:52:10 +030082 # The flag is needed for legacy asyncio.iscoroutine()
83 __iter__._is_coroutine = coroutines._is_coroutine
84
Andrew Svetlov5f841b52017-12-09 00:23:48 +020085 async def __acquire_ctx(self):
86 await self.acquire()
Victor Stinner3f438a92017-11-28 14:43:52 +010087 return _ContextManager(self)
Yury Selivanovd08c3632015-05-13 15:15:56 -040088
Andrew Svetlov5f841b52017-12-09 00:23:48 +020089 def __await__(self):
Andrew Svetlov28d8d142017-12-09 20:00:05 +020090 warnings.warn("'with await lock' is deprecated "
91 "use 'async with lock' instead",
92 DeprecationWarning, stacklevel=2)
Andrew Svetlov5f841b52017-12-09 00:23:48 +020093 # To make "with await lock" work.
94 return self.__acquire_ctx().__await__()
95
96 async def __aenter__(self):
97 await self.acquire()
Victor Stinner3f438a92017-11-28 14:43:52 +010098 # We have no use for the "as ..." clause in the with
99 # statement for locks.
100 return None
Yury Selivanovd08c3632015-05-13 15:15:56 -0400101
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200102 async def __aexit__(self, exc_type, exc, tb):
Victor Stinner3f438a92017-11-28 14:43:52 +0100103 self.release()
Yury Selivanovd08c3632015-05-13 15:15:56 -0400104
105
106class Lock(_ContextManagerMixin):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700107 """Primitive lock objects.
108
109 A primitive lock is a synchronization primitive that is not owned
110 by a particular coroutine when locked. A primitive lock is in one
111 of two states, 'locked' or 'unlocked'.
112
113 It is created in the unlocked state. It has two basic methods,
114 acquire() and release(). When the state is unlocked, acquire()
115 changes the state to locked and returns immediately. When the
116 state is locked, acquire() blocks until a call to release() in
117 another coroutine changes it to unlocked, then the acquire() call
118 resets it to locked and returns. The release() method should only
119 be called in the locked state; it changes the state to unlocked
120 and returns immediately. If an attempt is made to release an
121 unlocked lock, a RuntimeError will be raised.
122
123 When more than one coroutine is blocked in acquire() waiting for
124 the state to turn to unlocked, only one coroutine proceeds when a
125 release() call resets the state to unlocked; first coroutine which
126 is blocked in acquire() is being processed.
127
Andrew Svetlov88743422017-12-11 17:35:49 +0200128 acquire() is a coroutine and should be called with 'await'.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700129
Andrew Svetlov88743422017-12-11 17:35:49 +0200130 Locks also support the asynchronous context management protocol.
131 'async with lock' statement should be used.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700132
133 Usage:
134
135 lock = Lock()
136 ...
Andrew Svetlov88743422017-12-11 17:35:49 +0200137 await lock.acquire()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700138 try:
139 ...
140 finally:
141 lock.release()
142
143 Context manager usage:
144
145 lock = Lock()
146 ...
Andrew Svetlov88743422017-12-11 17:35:49 +0200147 async with lock:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700148 ...
149
150 Lock objects can be tested for locking state:
151
152 if not lock.locked():
Andrew Svetlov88743422017-12-11 17:35:49 +0200153 await lock.acquire()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700154 else:
155 # lock is acquired
156 ...
157
158 """
159
160 def __init__(self, *, loop=None):
Miss Islington (bot)87a865e2019-06-05 03:17:42 -0700161 self._waiters = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700162 self._locked = False
163 if loop is not None:
164 self._loop = loop
165 else:
166 self._loop = events.get_event_loop()
167
168 def __repr__(self):
169 res = super().__repr__()
170 extra = 'locked' if self._locked else 'unlocked'
171 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500172 extra = f'{extra}, waiters:{len(self._waiters)}'
173 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700174
175 def locked(self):
Victor Stinnerc37dd612013-12-02 14:31:16 +0100176 """Return True if lock is acquired."""
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700177 return self._locked
178
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200179 async def acquire(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700180 """Acquire a lock.
181
182 This method blocks until the lock is unlocked, then sets it to
183 locked and returns True.
184 """
Miss Islington (bot)87a865e2019-06-05 03:17:42 -0700185 if (not self._locked and (self._waiters is None or
186 all(w.cancelled() for w in self._waiters))):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700187 self._locked = True
188 return True
189
Miss Islington (bot)87a865e2019-06-05 03:17:42 -0700190 if self._waiters is None:
191 self._waiters = collections.deque()
Yury Selivanov7661db62016-05-16 15:38:39 -0400192 fut = self._loop.create_future()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700193 self._waiters.append(fut)
Bar Harel2f79c012018-02-03 00:04:00 +0200194
195 # Finally block should be called before the CancelledError
196 # handling as we don't want CancelledError to call
197 # _wake_up_first() and attempt to wake up itself.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700198 try:
Bar Harel2f79c012018-02-03 00:04:00 +0200199 try:
200 await fut
201 finally:
202 self._waiters.remove(fut)
Andrew Svetlov0baa72f2018-09-11 10:13:04 -0700203 except exceptions.CancelledError:
Mathieu Sornay894a6542017-06-09 22:17:40 +0200204 if not self._locked:
205 self._wake_up_first()
206 raise
Bar Harel2f79c012018-02-03 00:04:00 +0200207
208 self._locked = True
209 return True
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700210
211 def release(self):
212 """Release a lock.
213
214 When the lock is locked, reset it to unlocked, and return.
215 If any other coroutines are blocked waiting for the lock to become
216 unlocked, allow exactly one of them to proceed.
217
218 When invoked on an unlocked lock, a RuntimeError is raised.
219
220 There is no return value.
221 """
222 if self._locked:
223 self._locked = False
Mathieu Sornay894a6542017-06-09 22:17:40 +0200224 self._wake_up_first()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700225 else:
226 raise RuntimeError('Lock is not acquired.')
227
Mathieu Sornay894a6542017-06-09 22:17:40 +0200228 def _wake_up_first(self):
Bar Harel2f79c012018-02-03 00:04:00 +0200229 """Wake up the first waiter if it isn't done."""
Miss Islington (bot)87a865e2019-06-05 03:17:42 -0700230 if not self._waiters:
231 return
Bar Harel2f79c012018-02-03 00:04:00 +0200232 try:
233 fut = next(iter(self._waiters))
234 except StopIteration:
235 return
236
237 # .done() necessarily means that a waiter will wake up later on and
238 # either take the lock, or, if it was cancelled and lock wasn't
239 # taken already, will hit this again and wake up a new waiter.
240 if not fut.done():
241 fut.set_result(True)
Mathieu Sornay894a6542017-06-09 22:17:40 +0200242
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700243
244class Event:
Guido van Rossum994bf432013-12-19 12:47:38 -0800245 """Asynchronous equivalent to threading.Event.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700246
247 Class implementing event objects. An event manages a flag that can be set
248 to true with the set() method and reset to false with the clear() method.
249 The wait() method blocks until the flag is true. The flag is initially
250 false.
251 """
252
253 def __init__(self, *, loop=None):
254 self._waiters = collections.deque()
255 self._value = False
256 if loop is not None:
257 self._loop = loop
258 else:
259 self._loop = events.get_event_loop()
260
261 def __repr__(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700262 res = super().__repr__()
Guido van Rossumccea0842013-11-04 13:18:19 -0800263 extra = 'set' if self._value else 'unset'
264 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500265 extra = f'{extra}, waiters:{len(self._waiters)}'
266 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700267
268 def is_set(self):
Victor Stinnerc37dd612013-12-02 14:31:16 +0100269 """Return True if and only if the internal flag is true."""
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700270 return self._value
271
272 def set(self):
273 """Set the internal flag to true. All coroutines waiting for it to
274 become true are awakened. Coroutine that call wait() once the flag is
275 true will not block at all.
276 """
277 if not self._value:
278 self._value = True
279
280 for fut in self._waiters:
281 if not fut.done():
282 fut.set_result(True)
283
284 def clear(self):
285 """Reset the internal flag to false. Subsequently, coroutines calling
286 wait() will block until set() is called to set the internal flag
287 to true again."""
288 self._value = False
289
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200290 async def wait(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700291 """Block until the internal flag is true.
292
293 If the internal flag is true on entry, return True
294 immediately. Otherwise, block until another coroutine calls
295 set() to set the flag to true, then return True.
296 """
297 if self._value:
298 return True
299
Yury Selivanov7661db62016-05-16 15:38:39 -0400300 fut = self._loop.create_future()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700301 self._waiters.append(fut)
302 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200303 await fut
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700304 return True
305 finally:
306 self._waiters.remove(fut)
307
308
Yury Selivanovd08c3632015-05-13 15:15:56 -0400309class Condition(_ContextManagerMixin):
Guido van Rossum994bf432013-12-19 12:47:38 -0800310 """Asynchronous equivalent to threading.Condition.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700311
312 This class implements condition variable objects. A condition variable
313 allows one or more coroutines to wait until they are notified by another
314 coroutine.
Guido van Rossumccea0842013-11-04 13:18:19 -0800315
316 A new Lock object is created and used as the underlying lock.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700317 """
318
Andrew Svetlovf21fcd02014-07-26 17:54:34 +0300319 def __init__(self, lock=None, *, loop=None):
Guido van Rossumccea0842013-11-04 13:18:19 -0800320 if loop is not None:
321 self._loop = loop
322 else:
323 self._loop = events.get_event_loop()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700324
Andrew Svetlovf21fcd02014-07-26 17:54:34 +0300325 if lock is None:
326 lock = Lock(loop=self._loop)
327 elif lock._loop is not self._loop:
328 raise ValueError("loop argument must agree with lock")
329
Guido van Rossumccea0842013-11-04 13:18:19 -0800330 self._lock = lock
331 # Export the lock's locked(), acquire() and release() methods.
332 self.locked = lock.locked
333 self.acquire = lock.acquire
334 self.release = lock.release
335
336 self._waiters = collections.deque()
337
338 def __repr__(self):
339 res = super().__repr__()
340 extra = 'locked' if self.locked() else 'unlocked'
341 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500342 extra = f'{extra}, waiters:{len(self._waiters)}'
343 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700344
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200345 async def wait(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700346 """Wait until notified.
347
348 If the calling coroutine has not acquired the lock when this
349 method is called, a RuntimeError is raised.
350
351 This method releases the underlying lock, and then blocks
352 until it is awakened by a notify() or notify_all() call for
353 the same condition variable in another coroutine. Once
354 awakened, it re-acquires the lock and returns True.
355 """
Guido van Rossumccea0842013-11-04 13:18:19 -0800356 if not self.locked():
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700357 raise RuntimeError('cannot wait on un-acquired lock')
358
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700359 self.release()
360 try:
Yury Selivanov7661db62016-05-16 15:38:39 -0400361 fut = self._loop.create_future()
Guido van Rossumccea0842013-11-04 13:18:19 -0800362 self._waiters.append(fut)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700363 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200364 await fut
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700365 return True
366 finally:
Guido van Rossumccea0842013-11-04 13:18:19 -0800367 self._waiters.remove(fut)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700368
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700369 finally:
Yury Selivanovc92bf832016-06-11 12:00:07 -0400370 # Must reacquire lock even if wait is cancelled
Bar Harel57465102018-02-14 11:18:11 +0200371 cancelled = False
Yury Selivanovc92bf832016-06-11 12:00:07 -0400372 while True:
373 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200374 await self.acquire()
Yury Selivanovc92bf832016-06-11 12:00:07 -0400375 break
Andrew Svetlov0baa72f2018-09-11 10:13:04 -0700376 except exceptions.CancelledError:
Bar Harel57465102018-02-14 11:18:11 +0200377 cancelled = True
378
379 if cancelled:
Andrew Svetlov0baa72f2018-09-11 10:13:04 -0700380 raise exceptions.CancelledError
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700381
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200382 async def wait_for(self, predicate):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700383 """Wait until a predicate becomes true.
384
385 The predicate should be a callable which result will be
386 interpreted as a boolean value. The final predicate value is
387 the return value.
388 """
389 result = predicate()
390 while not result:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200391 await self.wait()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700392 result = predicate()
393 return result
394
395 def notify(self, n=1):
396 """By default, wake up one coroutine waiting on this condition, if any.
397 If the calling coroutine has not acquired the lock when this method
398 is called, a RuntimeError is raised.
399
400 This method wakes up at most n of the coroutines waiting for the
401 condition variable; it is a no-op if no coroutines are waiting.
402
403 Note: an awakened coroutine does not actually return from its
404 wait() call until it can reacquire the lock. Since notify() does
405 not release the lock, its caller should.
406 """
Guido van Rossumccea0842013-11-04 13:18:19 -0800407 if not self.locked():
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700408 raise RuntimeError('cannot notify on un-acquired lock')
409
410 idx = 0
Guido van Rossumccea0842013-11-04 13:18:19 -0800411 for fut in self._waiters:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700412 if idx >= n:
413 break
414
415 if not fut.done():
416 idx += 1
417 fut.set_result(False)
418
419 def notify_all(self):
420 """Wake up all threads waiting on this condition. This method acts
421 like notify(), but wakes up all waiting threads instead of one. If the
422 calling thread has not acquired the lock when this method is called,
423 a RuntimeError is raised.
424 """
Guido van Rossumccea0842013-11-04 13:18:19 -0800425 self.notify(len(self._waiters))
426
Guido van Rossumccea0842013-11-04 13:18:19 -0800427
Yury Selivanovd08c3632015-05-13 15:15:56 -0400428class Semaphore(_ContextManagerMixin):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700429 """A Semaphore implementation.
430
431 A semaphore manages an internal counter which is decremented by each
432 acquire() call and incremented by each release() call. The counter
433 can never go below zero; when acquire() finds that it is zero, it blocks,
434 waiting until some other thread calls release().
435
Serhiy Storchaka14867992014-09-10 23:43:41 +0300436 Semaphores also support the context management protocol.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700437
Guido van Rossum085869b2013-11-23 15:09:16 -0800438 The optional argument gives the initial value for the internal
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700439 counter; it defaults to 1. If the value given is less than 0,
440 ValueError is raised.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700441 """
442
Guido van Rossum085869b2013-11-23 15:09:16 -0800443 def __init__(self, value=1, *, loop=None):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700444 if value < 0:
Guido van Rossum9c55a582013-11-21 11:07:45 -0800445 raise ValueError("Semaphore initial value must be >= 0")
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700446 self._value = value
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700447 self._waiters = collections.deque()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700448 if loop is not None:
449 self._loop = loop
450 else:
451 self._loop = events.get_event_loop()
452
453 def __repr__(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700454 res = super().__repr__()
Yury Selivanov6370f342017-12-10 18:36:12 -0500455 extra = 'locked' if self.locked() else f'unlocked, value:{self._value}'
Guido van Rossumccea0842013-11-04 13:18:19 -0800456 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500457 extra = f'{extra}, waiters:{len(self._waiters)}'
458 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700459
Guido van Rossumd455a502015-09-29 11:54:45 -0700460 def _wake_up_next(self):
461 while self._waiters:
462 waiter = self._waiters.popleft()
463 if not waiter.done():
464 waiter.set_result(None)
465 return
466
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700467 def locked(self):
468 """Returns True if semaphore can not be acquired immediately."""
Guido van Rossumab3c8892014-01-25 16:51:57 -0800469 return self._value == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700470
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200471 async def acquire(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700472 """Acquire a semaphore.
473
474 If the internal counter is larger than zero on entry,
475 decrement it by one and return True immediately. If it is
476 zero on entry, block, waiting until some other coroutine has
477 called release() to make it larger than 0, and then return
478 True.
479 """
Guido van Rossumd455a502015-09-29 11:54:45 -0700480 while self._value <= 0:
Yury Selivanov7661db62016-05-16 15:38:39 -0400481 fut = self._loop.create_future()
Guido van Rossumd455a502015-09-29 11:54:45 -0700482 self._waiters.append(fut)
483 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200484 await fut
Guido van Rossumd455a502015-09-29 11:54:45 -0700485 except:
486 # See the similar code in Queue.get.
487 fut.cancel()
488 if self._value > 0 and not fut.cancelled():
489 self._wake_up_next()
490 raise
491 self._value -= 1
492 return True
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700493
494 def release(self):
495 """Release a semaphore, incrementing the internal counter by one.
496 When it was zero on entry and another coroutine is waiting for it to
497 become larger than zero again, wake up that coroutine.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700498 """
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700499 self._value += 1
Guido van Rossumd455a502015-09-29 11:54:45 -0700500 self._wake_up_next()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700501
Guido van Rossum085869b2013-11-23 15:09:16 -0800502
503class BoundedSemaphore(Semaphore):
504 """A bounded semaphore implementation.
505
506 This raises ValueError in release() if it would increase the value
507 above the initial value.
508 """
509
510 def __init__(self, value=1, *, loop=None):
511 self._bound_value = value
512 super().__init__(value, loop=loop)
513
514 def release(self):
515 if self._value >= self._bound_value:
516 raise ValueError('BoundedSemaphore released too many times')
517 super().release()