blob: d94daeb5a173f574f8509a366f1bcb9337e487ba [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):
Zackery Spytz9aa78562019-06-05 03:33:27 -0600161 self._waiters = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700162 self._locked = False
Emmanuel Arias537877d2019-09-10 07:55:07 -0300163 if loop is None:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700164 self._loop = events.get_event_loop()
Emmanuel Arias537877d2019-09-10 07:55:07 -0300165 else:
166 self._loop = loop
167 warnings.warn("The loop argument is deprecated since Python 3.8, "
168 "and scheduled for removal in Python 3.10.",
169 DeprecationWarning, stacklevel=2)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700170
171 def __repr__(self):
172 res = super().__repr__()
173 extra = 'locked' if self._locked else 'unlocked'
174 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500175 extra = f'{extra}, waiters:{len(self._waiters)}'
176 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700177
178 def locked(self):
Victor Stinnerc37dd612013-12-02 14:31:16 +0100179 """Return True if lock is acquired."""
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700180 return self._locked
181
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200182 async def acquire(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700183 """Acquire a lock.
184
185 This method blocks until the lock is unlocked, then sets it to
186 locked and returns True.
187 """
Zackery Spytz9aa78562019-06-05 03:33:27 -0600188 if (not self._locked and (self._waiters is None or
189 all(w.cancelled() for w in self._waiters))):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700190 self._locked = True
191 return True
192
Zackery Spytz9aa78562019-06-05 03:33:27 -0600193 if self._waiters is None:
194 self._waiters = collections.deque()
Yury Selivanov7661db62016-05-16 15:38:39 -0400195 fut = self._loop.create_future()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700196 self._waiters.append(fut)
Bar Harel2f79c012018-02-03 00:04:00 +0200197
198 # Finally block should be called before the CancelledError
199 # handling as we don't want CancelledError to call
200 # _wake_up_first() and attempt to wake up itself.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700201 try:
Bar Harel2f79c012018-02-03 00:04:00 +0200202 try:
203 await fut
204 finally:
205 self._waiters.remove(fut)
Andrew Svetlov0baa72f2018-09-11 10:13:04 -0700206 except exceptions.CancelledError:
Mathieu Sornay894a6542017-06-09 22:17:40 +0200207 if not self._locked:
208 self._wake_up_first()
209 raise
Bar Harel2f79c012018-02-03 00:04:00 +0200210
211 self._locked = True
212 return True
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700213
214 def release(self):
215 """Release a lock.
216
217 When the lock is locked, reset it to unlocked, and return.
218 If any other coroutines are blocked waiting for the lock to become
219 unlocked, allow exactly one of them to proceed.
220
221 When invoked on an unlocked lock, a RuntimeError is raised.
222
223 There is no return value.
224 """
225 if self._locked:
226 self._locked = False
Mathieu Sornay894a6542017-06-09 22:17:40 +0200227 self._wake_up_first()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700228 else:
229 raise RuntimeError('Lock is not acquired.')
230
Mathieu Sornay894a6542017-06-09 22:17:40 +0200231 def _wake_up_first(self):
Bar Harel2f79c012018-02-03 00:04:00 +0200232 """Wake up the first waiter if it isn't done."""
Zackery Spytz9aa78562019-06-05 03:33:27 -0600233 if not self._waiters:
234 return
Bar Harel2f79c012018-02-03 00:04:00 +0200235 try:
236 fut = next(iter(self._waiters))
237 except StopIteration:
238 return
239
240 # .done() necessarily means that a waiter will wake up later on and
241 # either take the lock, or, if it was cancelled and lock wasn't
242 # taken already, will hit this again and wake up a new waiter.
243 if not fut.done():
244 fut.set_result(True)
Mathieu Sornay894a6542017-06-09 22:17:40 +0200245
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700246
247class Event:
Guido van Rossum994bf432013-12-19 12:47:38 -0800248 """Asynchronous equivalent to threading.Event.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700249
250 Class implementing event objects. An event manages a flag that can be set
251 to true with the set() method and reset to false with the clear() method.
252 The wait() method blocks until the flag is true. The flag is initially
253 false.
254 """
255
256 def __init__(self, *, loop=None):
257 self._waiters = collections.deque()
258 self._value = False
Emmanuel Arias537877d2019-09-10 07:55:07 -0300259 if loop is None:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700260 self._loop = events.get_event_loop()
Emmanuel Arias537877d2019-09-10 07:55:07 -0300261 else:
262 self._loop = loop
263 warnings.warn("The loop argument is deprecated since Python 3.8, "
264 "and scheduled for removal in Python 3.10.",
265 DeprecationWarning, stacklevel=2)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700266
267 def __repr__(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700268 res = super().__repr__()
Guido van Rossumccea0842013-11-04 13:18:19 -0800269 extra = 'set' if self._value else 'unset'
270 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500271 extra = f'{extra}, waiters:{len(self._waiters)}'
272 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700273
274 def is_set(self):
Victor Stinnerc37dd612013-12-02 14:31:16 +0100275 """Return True if and only if the internal flag is true."""
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700276 return self._value
277
278 def set(self):
279 """Set the internal flag to true. All coroutines waiting for it to
280 become true are awakened. Coroutine that call wait() once the flag is
281 true will not block at all.
282 """
283 if not self._value:
284 self._value = True
285
286 for fut in self._waiters:
287 if not fut.done():
288 fut.set_result(True)
289
290 def clear(self):
291 """Reset the internal flag to false. Subsequently, coroutines calling
292 wait() will block until set() is called to set the internal flag
293 to true again."""
294 self._value = False
295
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200296 async def wait(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700297 """Block until the internal flag is true.
298
299 If the internal flag is true on entry, return True
300 immediately. Otherwise, block until another coroutine calls
301 set() to set the flag to true, then return True.
302 """
303 if self._value:
304 return True
305
Yury Selivanov7661db62016-05-16 15:38:39 -0400306 fut = self._loop.create_future()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700307 self._waiters.append(fut)
308 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200309 await fut
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700310 return True
311 finally:
312 self._waiters.remove(fut)
313
314
Yury Selivanovd08c3632015-05-13 15:15:56 -0400315class Condition(_ContextManagerMixin):
Guido van Rossum994bf432013-12-19 12:47:38 -0800316 """Asynchronous equivalent to threading.Condition.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700317
318 This class implements condition variable objects. A condition variable
319 allows one or more coroutines to wait until they are notified by another
320 coroutine.
Guido van Rossumccea0842013-11-04 13:18:19 -0800321
322 A new Lock object is created and used as the underlying lock.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700323 """
324
Andrew Svetlovf21fcd02014-07-26 17:54:34 +0300325 def __init__(self, lock=None, *, loop=None):
Emmanuel Arias537877d2019-09-10 07:55:07 -0300326 if loop is None:
Guido van Rossumccea0842013-11-04 13:18:19 -0800327 self._loop = events.get_event_loop()
Emmanuel Arias537877d2019-09-10 07:55:07 -0300328 else:
329 self._loop = loop
330 warnings.warn("The loop argument is deprecated since Python 3.8, "
331 "and scheduled for removal in Python 3.10.",
332 DeprecationWarning, stacklevel=2)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700333
Andrew Svetlovf21fcd02014-07-26 17:54:34 +0300334 if lock is None:
Andrew Svetlov7264e922019-09-11 11:20:24 +0300335 lock = Lock(loop=loop)
Andrew Svetlovf21fcd02014-07-26 17:54:34 +0300336 elif lock._loop is not self._loop:
337 raise ValueError("loop argument must agree with lock")
338
Guido van Rossumccea0842013-11-04 13:18:19 -0800339 self._lock = lock
340 # Export the lock's locked(), acquire() and release() methods.
341 self.locked = lock.locked
342 self.acquire = lock.acquire
343 self.release = lock.release
344
345 self._waiters = collections.deque()
346
347 def __repr__(self):
348 res = super().__repr__()
349 extra = 'locked' if self.locked() else 'unlocked'
350 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500351 extra = f'{extra}, waiters:{len(self._waiters)}'
352 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700353
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200354 async def wait(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700355 """Wait until notified.
356
357 If the calling coroutine has not acquired the lock when this
358 method is called, a RuntimeError is raised.
359
360 This method releases the underlying lock, and then blocks
361 until it is awakened by a notify() or notify_all() call for
362 the same condition variable in another coroutine. Once
363 awakened, it re-acquires the lock and returns True.
364 """
Guido van Rossumccea0842013-11-04 13:18:19 -0800365 if not self.locked():
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700366 raise RuntimeError('cannot wait on un-acquired lock')
367
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700368 self.release()
369 try:
Yury Selivanov7661db62016-05-16 15:38:39 -0400370 fut = self._loop.create_future()
Guido van Rossumccea0842013-11-04 13:18:19 -0800371 self._waiters.append(fut)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700372 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200373 await fut
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700374 return True
375 finally:
Guido van Rossumccea0842013-11-04 13:18:19 -0800376 self._waiters.remove(fut)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700377
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700378 finally:
Yury Selivanovc92bf832016-06-11 12:00:07 -0400379 # Must reacquire lock even if wait is cancelled
Bar Harel57465102018-02-14 11:18:11 +0200380 cancelled = False
Yury Selivanovc92bf832016-06-11 12:00:07 -0400381 while True:
382 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200383 await self.acquire()
Yury Selivanovc92bf832016-06-11 12:00:07 -0400384 break
Andrew Svetlov0baa72f2018-09-11 10:13:04 -0700385 except exceptions.CancelledError:
Bar Harel57465102018-02-14 11:18:11 +0200386 cancelled = True
387
388 if cancelled:
Andrew Svetlov0baa72f2018-09-11 10:13:04 -0700389 raise exceptions.CancelledError
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700390
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200391 async def wait_for(self, predicate):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700392 """Wait until a predicate becomes true.
393
394 The predicate should be a callable which result will be
395 interpreted as a boolean value. The final predicate value is
396 the return value.
397 """
398 result = predicate()
399 while not result:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200400 await self.wait()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700401 result = predicate()
402 return result
403
404 def notify(self, n=1):
405 """By default, wake up one coroutine waiting on this condition, if any.
406 If the calling coroutine has not acquired the lock when this method
407 is called, a RuntimeError is raised.
408
409 This method wakes up at most n of the coroutines waiting for the
410 condition variable; it is a no-op if no coroutines are waiting.
411
412 Note: an awakened coroutine does not actually return from its
413 wait() call until it can reacquire the lock. Since notify() does
414 not release the lock, its caller should.
415 """
Guido van Rossumccea0842013-11-04 13:18:19 -0800416 if not self.locked():
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700417 raise RuntimeError('cannot notify on un-acquired lock')
418
419 idx = 0
Guido van Rossumccea0842013-11-04 13:18:19 -0800420 for fut in self._waiters:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700421 if idx >= n:
422 break
423
424 if not fut.done():
425 idx += 1
426 fut.set_result(False)
427
428 def notify_all(self):
429 """Wake up all threads waiting on this condition. This method acts
430 like notify(), but wakes up all waiting threads instead of one. If the
431 calling thread has not acquired the lock when this method is called,
432 a RuntimeError is raised.
433 """
Guido van Rossumccea0842013-11-04 13:18:19 -0800434 self.notify(len(self._waiters))
435
Guido van Rossumccea0842013-11-04 13:18:19 -0800436
Yury Selivanovd08c3632015-05-13 15:15:56 -0400437class Semaphore(_ContextManagerMixin):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700438 """A Semaphore implementation.
439
440 A semaphore manages an internal counter which is decremented by each
441 acquire() call and incremented by each release() call. The counter
442 can never go below zero; when acquire() finds that it is zero, it blocks,
443 waiting until some other thread calls release().
444
Serhiy Storchaka14867992014-09-10 23:43:41 +0300445 Semaphores also support the context management protocol.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700446
Guido van Rossum085869b2013-11-23 15:09:16 -0800447 The optional argument gives the initial value for the internal
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700448 counter; it defaults to 1. If the value given is less than 0,
449 ValueError is raised.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700450 """
451
Guido van Rossum085869b2013-11-23 15:09:16 -0800452 def __init__(self, value=1, *, loop=None):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700453 if value < 0:
Guido van Rossum9c55a582013-11-21 11:07:45 -0800454 raise ValueError("Semaphore initial value must be >= 0")
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700455 self._value = value
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700456 self._waiters = collections.deque()
Emmanuel Arias537877d2019-09-10 07:55:07 -0300457 if loop is None:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700458 self._loop = events.get_event_loop()
Emmanuel Arias537877d2019-09-10 07:55:07 -0300459 else:
460 self._loop = loop
461 warnings.warn("The loop argument is deprecated since Python 3.8, "
462 "and scheduled for removal in Python 3.10.",
463 DeprecationWarning, stacklevel=2)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700464
465 def __repr__(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700466 res = super().__repr__()
Yury Selivanov6370f342017-12-10 18:36:12 -0500467 extra = 'locked' if self.locked() else f'unlocked, value:{self._value}'
Guido van Rossumccea0842013-11-04 13:18:19 -0800468 if self._waiters:
Yury Selivanov6370f342017-12-10 18:36:12 -0500469 extra = f'{extra}, waiters:{len(self._waiters)}'
470 return f'<{res[1:-1]} [{extra}]>'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700471
Guido van Rossumd455a502015-09-29 11:54:45 -0700472 def _wake_up_next(self):
473 while self._waiters:
474 waiter = self._waiters.popleft()
475 if not waiter.done():
476 waiter.set_result(None)
477 return
478
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700479 def locked(self):
480 """Returns True if semaphore can not be acquired immediately."""
Guido van Rossumab3c8892014-01-25 16:51:57 -0800481 return self._value == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700482
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200483 async def acquire(self):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700484 """Acquire a semaphore.
485
486 If the internal counter is larger than zero on entry,
487 decrement it by one and return True immediately. If it is
488 zero on entry, block, waiting until some other coroutine has
489 called release() to make it larger than 0, and then return
490 True.
491 """
Guido van Rossumd455a502015-09-29 11:54:45 -0700492 while self._value <= 0:
Yury Selivanov7661db62016-05-16 15:38:39 -0400493 fut = self._loop.create_future()
Guido van Rossumd455a502015-09-29 11:54:45 -0700494 self._waiters.append(fut)
495 try:
Andrew Svetlov5f841b52017-12-09 00:23:48 +0200496 await fut
Guido van Rossumd455a502015-09-29 11:54:45 -0700497 except:
498 # See the similar code in Queue.get.
499 fut.cancel()
500 if self._value > 0 and not fut.cancelled():
501 self._wake_up_next()
502 raise
503 self._value -= 1
504 return True
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700505
506 def release(self):
507 """Release a semaphore, incrementing the internal counter by one.
508 When it was zero on entry and another coroutine is waiting for it to
509 become larger than zero again, wake up that coroutine.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700510 """
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700511 self._value += 1
Guido van Rossumd455a502015-09-29 11:54:45 -0700512 self._wake_up_next()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700513
Guido van Rossum085869b2013-11-23 15:09:16 -0800514
515class BoundedSemaphore(Semaphore):
516 """A bounded semaphore implementation.
517
518 This raises ValueError in release() if it would increase the value
519 above the initial value.
520 """
521
522 def __init__(self, value=1, *, loop=None):
Emmanuel Arias537877d2019-09-10 07:55:07 -0300523 if loop:
524 warnings.warn("The loop argument is deprecated since Python 3.8, "
525 "and scheduled for removal in Python 3.10.",
526 DeprecationWarning, stacklevel=2)
527
Guido van Rossum085869b2013-11-23 15:09:16 -0800528 self._bound_value = value
529 super().__init__(value, loop=loop)
530
531 def release(self):
532 if self._value >= self._bound_value:
533 raise ValueError('BoundedSemaphore released too many times')
534 super().release()