mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
bpo-32253: Deprecate with statement and bare await for asyncio locks (GH-4764)
* Add test for 'with (yield from lock)' * Deprecate with statement for asyncio locks * Document the deprecation
This commit is contained in:
parent
a9f8df646a
commit
28d8d14013
5 changed files with 108 additions and 55 deletions
|
@ -23,11 +23,9 @@ module (:class:`~threading.Lock`, :class:`~threading.Event`,
|
||||||
:class:`~threading.BoundedSemaphore`), but it has no *timeout* parameter. The
|
:class:`~threading.BoundedSemaphore`), but it has no *timeout* parameter. The
|
||||||
:func:`asyncio.wait_for` function can be used to cancel a task after a timeout.
|
:func:`asyncio.wait_for` function can be used to cancel a task after a timeout.
|
||||||
|
|
||||||
Locks
|
|
||||||
-----
|
|
||||||
|
|
||||||
Lock
|
Lock
|
||||||
^^^^
|
----
|
||||||
|
|
||||||
.. class:: Lock(\*, loop=None)
|
.. class:: Lock(\*, loop=None)
|
||||||
|
|
||||||
|
@ -37,8 +35,9 @@ Lock
|
||||||
particular coroutine when locked. A primitive lock is in one of two states,
|
particular coroutine when locked. A primitive lock is in one of two states,
|
||||||
'locked' or 'unlocked'.
|
'locked' or 'unlocked'.
|
||||||
|
|
||||||
It is created in the unlocked state. It has two basic methods, :meth:`acquire`
|
The lock is created in the unlocked state.
|
||||||
and :meth:`release`. When the state is unlocked, acquire() changes the state to
|
It has two basic methods, :meth:`acquire` and :meth:`release`.
|
||||||
|
When the state is unlocked, acquire() changes the state to
|
||||||
locked and returns immediately. When the state is locked, acquire() blocks
|
locked and returns immediately. When the state is locked, acquire() blocks
|
||||||
until a call to release() in another coroutine changes it to unlocked, then
|
until a call to release() in another coroutine changes it to unlocked, then
|
||||||
the acquire() call resets it to locked and returns. The release() method
|
the acquire() call resets it to locked and returns. The release() method
|
||||||
|
@ -51,38 +50,12 @@ Lock
|
||||||
resets the state to unlocked; first coroutine which is blocked in acquire()
|
resets the state to unlocked; first coroutine which is blocked in acquire()
|
||||||
is being processed.
|
is being processed.
|
||||||
|
|
||||||
:meth:`acquire` is a coroutine and should be called with ``yield from``.
|
:meth:`acquire` is a coroutine and should be called with ``await``.
|
||||||
|
|
||||||
Locks also support the context management protocol. ``(yield from lock)``
|
Locks support the :ref:`context management protocol <async-with-locks>`.
|
||||||
should be used as the context manager expression.
|
|
||||||
|
|
||||||
This class is :ref:`not thread safe <asyncio-multithreading>`.
|
This class is :ref:`not thread safe <asyncio-multithreading>`.
|
||||||
|
|
||||||
Usage::
|
|
||||||
|
|
||||||
lock = Lock()
|
|
||||||
...
|
|
||||||
yield from lock
|
|
||||||
try:
|
|
||||||
...
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
Context manager usage::
|
|
||||||
|
|
||||||
lock = Lock()
|
|
||||||
...
|
|
||||||
with (yield from lock):
|
|
||||||
...
|
|
||||||
|
|
||||||
Lock objects can be tested for locking state::
|
|
||||||
|
|
||||||
if not lock.locked():
|
|
||||||
yield from lock
|
|
||||||
else:
|
|
||||||
# lock is acquired
|
|
||||||
...
|
|
||||||
|
|
||||||
.. method:: locked()
|
.. method:: locked()
|
||||||
|
|
||||||
Return ``True`` if the lock is acquired.
|
Return ``True`` if the lock is acquired.
|
||||||
|
@ -110,7 +83,7 @@ Lock
|
||||||
|
|
||||||
|
|
||||||
Event
|
Event
|
||||||
^^^^^
|
-----
|
||||||
|
|
||||||
.. class:: Event(\*, loop=None)
|
.. class:: Event(\*, loop=None)
|
||||||
|
|
||||||
|
@ -151,7 +124,7 @@ Event
|
||||||
|
|
||||||
|
|
||||||
Condition
|
Condition
|
||||||
^^^^^^^^^
|
---------
|
||||||
|
|
||||||
.. class:: Condition(lock=None, \*, loop=None)
|
.. class:: Condition(lock=None, \*, loop=None)
|
||||||
|
|
||||||
|
@ -166,6 +139,9 @@ Condition
|
||||||
object, and it is used as the underlying lock. Otherwise,
|
object, and it is used as the underlying lock. Otherwise,
|
||||||
a new :class:`Lock` object is created and used as the underlying lock.
|
a new :class:`Lock` object is created and used as the underlying lock.
|
||||||
|
|
||||||
|
Conditions support the :ref:`context management protocol
|
||||||
|
<async-with-locks>`.
|
||||||
|
|
||||||
This class is :ref:`not thread safe <asyncio-multithreading>`.
|
This class is :ref:`not thread safe <asyncio-multithreading>`.
|
||||||
|
|
||||||
.. coroutinemethod:: acquire()
|
.. coroutinemethod:: acquire()
|
||||||
|
@ -239,11 +215,8 @@ Condition
|
||||||
This method is a :ref:`coroutine <coroutine>`.
|
This method is a :ref:`coroutine <coroutine>`.
|
||||||
|
|
||||||
|
|
||||||
Semaphores
|
|
||||||
----------
|
|
||||||
|
|
||||||
Semaphore
|
Semaphore
|
||||||
^^^^^^^^^
|
---------
|
||||||
|
|
||||||
.. class:: Semaphore(value=1, \*, loop=None)
|
.. class:: Semaphore(value=1, \*, loop=None)
|
||||||
|
|
||||||
|
@ -254,12 +227,13 @@ Semaphore
|
||||||
counter can never go below zero; when :meth:`acquire` finds that it is zero,
|
counter can never go below zero; when :meth:`acquire` finds that it is zero,
|
||||||
it blocks, waiting until some other coroutine calls :meth:`release`.
|
it blocks, waiting until some other coroutine calls :meth:`release`.
|
||||||
|
|
||||||
Semaphores also support the context management protocol.
|
|
||||||
|
|
||||||
The optional argument gives the initial value for the internal counter; it
|
The optional argument gives the initial value for the internal counter; it
|
||||||
defaults to ``1``. If the value given is less than ``0``, :exc:`ValueError`
|
defaults to ``1``. If the value given is less than ``0``, :exc:`ValueError`
|
||||||
is raised.
|
is raised.
|
||||||
|
|
||||||
|
Semaphores support the :ref:`context management protocol
|
||||||
|
<async-with-locks>`.
|
||||||
|
|
||||||
This class is :ref:`not thread safe <asyncio-multithreading>`.
|
This class is :ref:`not thread safe <asyncio-multithreading>`.
|
||||||
|
|
||||||
.. coroutinemethod:: acquire()
|
.. coroutinemethod:: acquire()
|
||||||
|
@ -285,7 +259,7 @@ Semaphore
|
||||||
|
|
||||||
|
|
||||||
BoundedSemaphore
|
BoundedSemaphore
|
||||||
^^^^^^^^^^^^^^^^
|
----------------
|
||||||
|
|
||||||
.. class:: BoundedSemaphore(value=1, \*, loop=None)
|
.. class:: BoundedSemaphore(value=1, \*, loop=None)
|
||||||
|
|
||||||
|
@ -293,3 +267,39 @@ BoundedSemaphore
|
||||||
|
|
||||||
This raises :exc:`ValueError` in :meth:`~Semaphore.release` if it would
|
This raises :exc:`ValueError` in :meth:`~Semaphore.release` if it would
|
||||||
increase the value above the initial value.
|
increase the value above the initial value.
|
||||||
|
|
||||||
|
Bounded semapthores support the :ref:`context management
|
||||||
|
protocol <async-with-locks>`.
|
||||||
|
|
||||||
|
This class is :ref:`not thread safe <asyncio-multithreading>`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _async-with-locks:
|
||||||
|
|
||||||
|
Using locks, conditions and semaphores in the :keyword:`async with` statement
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
:class:`Lock`, :class:`Condition`, :class:`Semaphore`, and
|
||||||
|
:class:`BoundedSemaphore` objects can be used in :keyword:`async with`
|
||||||
|
statements.
|
||||||
|
|
||||||
|
The :meth:`acquire` method will be called when the block is entered,
|
||||||
|
and :meth:`release` will be called when the block is exited. Hence,
|
||||||
|
the following snippet::
|
||||||
|
|
||||||
|
async with lock:
|
||||||
|
# do something...
|
||||||
|
|
||||||
|
is equivalent to::
|
||||||
|
|
||||||
|
await lock.acquire()
|
||||||
|
try:
|
||||||
|
# do something...
|
||||||
|
finally:
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
.. deprecated:: 3.7
|
||||||
|
|
||||||
|
Lock acquiring using ``await lock`` or ``yield from lock`` and
|
||||||
|
:keyword:`with` statement (``with await lock``, ``with (yield from
|
||||||
|
lock)``) are deprecated.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
|
__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import warnings
|
||||||
|
|
||||||
from . import events
|
from . import events
|
||||||
from . import futures
|
from . import futures
|
||||||
|
@ -63,6 +64,9 @@ class _ContextManagerMixin:
|
||||||
# <block>
|
# <block>
|
||||||
# finally:
|
# finally:
|
||||||
# lock.release()
|
# lock.release()
|
||||||
|
warnings.warn("'with (yield from lock)' is deprecated "
|
||||||
|
"use 'async with lock' instead",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
yield from self.acquire()
|
yield from self.acquire()
|
||||||
return _ContextManager(self)
|
return _ContextManager(self)
|
||||||
|
|
||||||
|
@ -71,6 +75,9 @@ class _ContextManagerMixin:
|
||||||
return _ContextManager(self)
|
return _ContextManager(self)
|
||||||
|
|
||||||
def __await__(self):
|
def __await__(self):
|
||||||
|
warnings.warn("'with await lock' is deprecated "
|
||||||
|
"use 'async with lock' instead",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
# To make "with await lock" work.
|
# To make "with await lock" work.
|
||||||
return self.__acquire_ctx().__await__()
|
return self.__acquire_ctx().__await__()
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ class LockTests(test_utils.TestCase):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def acquire_lock():
|
def acquire_lock():
|
||||||
yield from lock
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
yield from lock
|
||||||
|
|
||||||
self.loop.run_until_complete(acquire_lock())
|
self.loop.run_until_complete(acquire_lock())
|
||||||
self.assertTrue(repr(lock).endswith('[locked]>'))
|
self.assertTrue(repr(lock).endswith('[locked]>'))
|
||||||
|
@ -53,7 +54,8 @@ class LockTests(test_utils.TestCase):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def acquire_lock():
|
def acquire_lock():
|
||||||
return (yield from lock)
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
return (yield from lock)
|
||||||
|
|
||||||
res = self.loop.run_until_complete(acquire_lock())
|
res = self.loop.run_until_complete(acquire_lock())
|
||||||
|
|
||||||
|
@ -63,6 +65,32 @@ class LockTests(test_utils.TestCase):
|
||||||
lock.release()
|
lock.release()
|
||||||
self.assertFalse(lock.locked())
|
self.assertFalse(lock.locked())
|
||||||
|
|
||||||
|
def test_lock_by_with_statement(self):
|
||||||
|
loop = asyncio.new_event_loop() # don't use TestLoop quirks
|
||||||
|
self.set_event_loop(loop)
|
||||||
|
primitives = [
|
||||||
|
asyncio.Lock(loop=loop),
|
||||||
|
asyncio.Condition(loop=loop),
|
||||||
|
asyncio.Semaphore(loop=loop),
|
||||||
|
asyncio.BoundedSemaphore(loop=loop),
|
||||||
|
]
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test(lock):
|
||||||
|
yield from asyncio.sleep(0.01, loop=loop)
|
||||||
|
self.assertFalse(lock.locked())
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
with (yield from lock) as _lock:
|
||||||
|
self.assertIs(_lock, None)
|
||||||
|
self.assertTrue(lock.locked())
|
||||||
|
yield from asyncio.sleep(0.01, loop=loop)
|
||||||
|
self.assertTrue(lock.locked())
|
||||||
|
self.assertFalse(lock.locked())
|
||||||
|
|
||||||
|
for primitive in primitives:
|
||||||
|
loop.run_until_complete(test(primitive))
|
||||||
|
self.assertFalse(primitive.locked())
|
||||||
|
|
||||||
def test_acquire(self):
|
def test_acquire(self):
|
||||||
lock = asyncio.Lock(loop=self.loop)
|
lock = asyncio.Lock(loop=self.loop)
|
||||||
result = []
|
result = []
|
||||||
|
@ -212,7 +240,8 @@ class LockTests(test_utils.TestCase):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def acquire_lock():
|
def acquire_lock():
|
||||||
return (yield from lock)
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
return (yield from lock)
|
||||||
|
|
||||||
with self.loop.run_until_complete(acquire_lock()):
|
with self.loop.run_until_complete(acquire_lock()):
|
||||||
self.assertTrue(lock.locked())
|
self.assertTrue(lock.locked())
|
||||||
|
@ -224,7 +253,8 @@ class LockTests(test_utils.TestCase):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def acquire_lock():
|
def acquire_lock():
|
||||||
return (yield from lock)
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
return (yield from lock)
|
||||||
|
|
||||||
# This spells "yield from lock" outside a generator.
|
# This spells "yield from lock" outside a generator.
|
||||||
cm = self.loop.run_until_complete(acquire_lock())
|
cm = self.loop.run_until_complete(acquire_lock())
|
||||||
|
@ -668,7 +698,8 @@ class ConditionTests(test_utils.TestCase):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def acquire_cond():
|
def acquire_cond():
|
||||||
return (yield from cond)
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
return (yield from cond)
|
||||||
|
|
||||||
with self.loop.run_until_complete(acquire_cond()):
|
with self.loop.run_until_complete(acquire_cond()):
|
||||||
self.assertTrue(cond.locked())
|
self.assertTrue(cond.locked())
|
||||||
|
@ -751,7 +782,8 @@ class SemaphoreTests(test_utils.TestCase):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def acquire_lock():
|
def acquire_lock():
|
||||||
return (yield from sem)
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
return (yield from sem)
|
||||||
|
|
||||||
res = self.loop.run_until_complete(acquire_lock())
|
res = self.loop.run_until_complete(acquire_lock())
|
||||||
|
|
||||||
|
@ -893,7 +925,8 @@ class SemaphoreTests(test_utils.TestCase):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def acquire_lock():
|
def acquire_lock():
|
||||||
return (yield from sem)
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
return (yield from sem)
|
||||||
|
|
||||||
with self.loop.run_until_complete(acquire_lock()):
|
with self.loop.run_until_complete(acquire_lock()):
|
||||||
self.assertFalse(sem.locked())
|
self.assertFalse(sem.locked())
|
||||||
|
|
|
@ -59,12 +59,13 @@ class LockTests(BaseTest):
|
||||||
async def test(lock):
|
async def test(lock):
|
||||||
await asyncio.sleep(0.01, loop=self.loop)
|
await asyncio.sleep(0.01, loop=self.loop)
|
||||||
self.assertFalse(lock.locked())
|
self.assertFalse(lock.locked())
|
||||||
with await lock as _lock:
|
with self.assertWarns(DeprecationWarning):
|
||||||
self.assertIs(_lock, None)
|
with await lock as _lock:
|
||||||
self.assertTrue(lock.locked())
|
self.assertIs(_lock, None)
|
||||||
await asyncio.sleep(0.01, loop=self.loop)
|
self.assertTrue(lock.locked())
|
||||||
self.assertTrue(lock.locked())
|
await asyncio.sleep(0.01, loop=self.loop)
|
||||||
self.assertFalse(lock.locked())
|
self.assertTrue(lock.locked())
|
||||||
|
self.assertFalse(lock.locked())
|
||||||
|
|
||||||
for primitive in primitives:
|
for primitive in primitives:
|
||||||
self.loop.run_until_complete(test(primitive))
|
self.loop.run_until_complete(test(primitive))
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Deprecate ``yield from lock``, ``await lock``, ``with (yield from lock)``
|
||||||
|
and ``with await lock`` for asyncio synchronization primitives.
|
Loading…
Add table
Add a link
Reference in a new issue