From 65e54226b527363c44f10c5f0e83dec3e95c736d Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Tue, 24 Nov 2020 21:30:43 +0200 Subject: [PATCH 1/4] Update code after merge review from 1st1 --- Lib/asyncio/locks.py | 8 ++++---- Lib/asyncio/mixins.py | 4 ++-- Lib/asyncio/queues.py | 2 +- Lib/test/test_asyncio/utils.py | 6 ++++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index 6f322c258cfe4f1..5b2f467beb6d24e 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -19,7 +19,7 @@ async def __aexit__(self, exc_type, exc, tb): self.release() -class Lock(_ContextManagerMixin, mixins._LoopBoundedMixin): +class Lock(_ContextManagerMixin, mixins._LoopBoundMixin): """Primitive lock objects. A primitive lock is a synchronization primitive that is not owned @@ -153,7 +153,7 @@ def _wake_up_first(self): fut.set_result(True) -class Event(mixins._LoopBoundedMixin): +class Event(mixins._LoopBoundMixin): """Asynchronous equivalent to threading.Event. Class implementing event objects. An event manages a flag that can be set @@ -214,7 +214,7 @@ async def wait(self): self._waiters.remove(fut) -class Condition(_ContextManagerMixin, mixins._LoopBoundedMixin): +class Condition(_ContextManagerMixin, mixins._LoopBoundMixin): """Asynchronous equivalent to threading.Condition. This class implements condition variable objects. A condition variable @@ -328,7 +328,7 @@ def notify_all(self): self.notify(len(self._waiters)) -class Semaphore(_ContextManagerMixin, mixins._LoopBoundedMixin): +class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin): """A Semaphore implementation. A semaphore manages an internal counter which is decremented by each diff --git a/Lib/asyncio/mixins.py b/Lib/asyncio/mixins.py index dbc4b5faccb019d..c6bf97329e92494 100644 --- a/Lib/asyncio/mixins.py +++ b/Lib/asyncio/mixins.py @@ -6,7 +6,7 @@ _global_lock = threading.Lock() -class _LoopBoundedMixin: +class _LoopBoundMixin: _loop = None def _get_loop(self): @@ -17,5 +17,5 @@ def _get_loop(self): if self._loop is None: self._loop = loop if loop is not self._loop: - raise RuntimeError(f'{type(self).__name__} have already bounded to another loop') + raise RuntimeError(f'{self!r} is bound to a different event loop') return loop diff --git a/Lib/asyncio/queues.py b/Lib/asyncio/queues.py index 78ae9e99ccf0e9b..eb03ff47abaf83c 100644 --- a/Lib/asyncio/queues.py +++ b/Lib/asyncio/queues.py @@ -17,7 +17,7 @@ class QueueFull(Exception): pass -class Queue(mixins._LoopBoundedMixin): +class Queue(mixins._LoopBoundMixin): """A queue, useful for coordinating producer and consumer coroutines. If maxsize is less than or equal to zero, the queue size is infinite. If it diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py index aba90c970a8e249..a0db54a878dcfa4 100644 --- a/Lib/test/test_asyncio/utils.py +++ b/Lib/test/test_asyncio/utils.py @@ -551,8 +551,10 @@ def _get_running_loop(): frame = sys._getframe(1) if frame.f_globals['__name__'] == 'asyncio.mixins': - # When we called from LoopBoundedMixin we should - # fallback to default implementation of get_running_loop + # When we called from LoopBoundMixin we should fall back + # to the default implementation of get_running_loop because + # we must return the currently running event loop + # rather than None try: return events.get_running_loop() except RuntimeError: From 21ba1ee787f948fe067c5f623e8fe51d921275eb Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Wed, 25 Nov 2020 00:44:07 +0200 Subject: [PATCH 2/4] Use a sentinel approach for loop parameter Remove unnecessary _ger_running_loop patching --- Lib/asyncio/locks.py | 27 ++++++++++++++++++++++----- Lib/test/test_asyncio/test_locks.py | 17 +++++++++++++++++ Lib/test/test_asyncio/utils.py | 23 ----------------------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index 5b2f467beb6d24e..1dbca4684914e8a 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -8,6 +8,18 @@ from . import mixins +# Used as a sentinel for loop parameter +_marker = object() + + +def _verify_parameter_is_marker(obj, parameter): + if parameter is not _marker: + raise TypeError( + f'As of 3.10, the *loop* parameter was removed from ' + f'{type(obj).__name__}() since it is no longer necessary' + ) + + class _ContextManagerMixin: async def __aenter__(self): await self.acquire() @@ -73,7 +85,8 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundMixin): """ - def __init__(self): + def __init__(self, *, loop=_marker): + _verify_parameter_is_marker(self, loop) self._waiters = None self._locked = False @@ -162,7 +175,8 @@ class Event(mixins._LoopBoundMixin): false. """ - def __init__(self): + def __init__(self, loop=_marker): + _verify_parameter_is_marker(self, loop) self._waiters = collections.deque() self._value = False @@ -224,7 +238,8 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundMixin): A new Lock object is created and used as the underlying lock. """ - def __init__(self, lock=None): + def __init__(self, lock=None, *, loop=_marker): + _verify_parameter_is_marker(self, loop) if lock is None: lock = Lock() elif lock._loop is not self._get_loop(): @@ -343,7 +358,8 @@ class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin): ValueError is raised. """ - def __init__(self, value=1): + def __init__(self, value=1, *, loop=_marker): + _verify_parameter_is_marker(self, loop) if value < 0: raise ValueError("Semaphore initial value must be >= 0") self._value = value @@ -406,7 +422,8 @@ class BoundedSemaphore(Semaphore): above the initial value. """ - def __init__(self, value=1): + def __init__(self, value=1, *, loop=_marker): + _verify_parameter_is_marker(self, loop) self._bound_value = value super().__init__(value) diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 6c34ef60e30b516..6194cd06176dac3 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -51,6 +51,23 @@ def acquire_lock(): self.assertFalse(lock.locked()) + def test_lock_doesnt_accept_loop_parameter(self): + primitives_cls = [ + asyncio.Lock, + asyncio.Condition, + asyncio.Event, + asyncio.Semaphore, + asyncio.BoundedSemaphore, + ] + + for cls in primitives_cls: + with self.assertRaisesRegex( + TypeError, + rf'As of 3.10, the \*loop\* parameter was removed from ' + rf'{cls.__name__}\(\) since it is no longer necessary' + ): + cls(loop=self.loop) + def test_lock_by_with_statement(self): loop = asyncio.new_event_loop() # don't use TestLoop quirks self.set_event_loop(loop) diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py index a0db54a878dcfa4..67180f7eb3955aa 100644 --- a/Lib/test/test_asyncio/utils.py +++ b/Lib/test/test_asyncio/utils.py @@ -541,33 +541,10 @@ def new_test_loop(self, gen=None): self.set_event_loop(loop) return loop - def unpatch_get_running_loop(self): - events._get_running_loop = self._get_running_loop - def setUp(self): - self._get_running_loop = events._get_running_loop - - def _get_running_loop(): - frame = sys._getframe(1) - - if frame.f_globals['__name__'] == 'asyncio.mixins': - # When we called from LoopBoundMixin we should fall back - # to the default implementation of get_running_loop because - # we must return the currently running event loop - # rather than None - try: - return events.get_running_loop() - except RuntimeError: - return None - - return None - - events._get_running_loop = _get_running_loop self._thread_cleanup = threading_helper.threading_setup() def tearDown(self): - self.unpatch_get_running_loop() - events.set_event_loop(None) # Detect CPython bug #23353: ensure that yield/yield-from is not used From fcee6608feaf47040cec0a7703f8c975b2659792 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Wed, 25 Nov 2020 08:46:04 +0200 Subject: [PATCH 3/4] Use more clear function name --- Lib/asyncio/locks.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index 1dbca4684914e8a..d7a4180e143e16e 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -12,8 +12,8 @@ _marker = object() -def _verify_parameter_is_marker(obj, parameter): - if parameter is not _marker: +def _verify_no_loop(obj, loop): + if loop is not _marker: raise TypeError( f'As of 3.10, the *loop* parameter was removed from ' f'{type(obj).__name__}() since it is no longer necessary' @@ -86,7 +86,7 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundMixin): """ def __init__(self, *, loop=_marker): - _verify_parameter_is_marker(self, loop) + _verify_no_loop(self, loop) self._waiters = None self._locked = False @@ -176,7 +176,7 @@ class Event(mixins._LoopBoundMixin): """ def __init__(self, loop=_marker): - _verify_parameter_is_marker(self, loop) + _verify_no_loop(self, loop) self._waiters = collections.deque() self._value = False @@ -239,7 +239,7 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundMixin): """ def __init__(self, lock=None, *, loop=_marker): - _verify_parameter_is_marker(self, loop) + _verify_no_loop(self, loop) if lock is None: lock = Lock() elif lock._loop is not self._get_loop(): @@ -359,7 +359,7 @@ class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin): """ def __init__(self, value=1, *, loop=_marker): - _verify_parameter_is_marker(self, loop) + _verify_no_loop(self, loop) if value < 0: raise ValueError("Semaphore initial value must be >= 0") self._value = value @@ -423,7 +423,7 @@ class BoundedSemaphore(Semaphore): """ def __init__(self, value=1, *, loop=_marker): - _verify_parameter_is_marker(self, loop) + _verify_no_loop(self, loop) self._bound_value = value super().__init__(value) From 7a6156e281dad7b84548b62ad62f72b78c8c310b Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Wed, 25 Nov 2020 09:46:19 +0200 Subject: [PATCH 4/4] Add init method to _LoopBoundMixin to check that loop param wasn't used --- Lib/asyncio/locks.py | 33 ++++++++++----------------------- Lib/asyncio/mixins.py | 10 ++++++++++ Lib/asyncio/queues.py | 3 ++- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index d7a4180e143e16e..a7453fb1c772878 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -8,18 +8,6 @@ from . import mixins -# Used as a sentinel for loop parameter -_marker = object() - - -def _verify_no_loop(obj, loop): - if loop is not _marker: - raise TypeError( - f'As of 3.10, the *loop* parameter was removed from ' - f'{type(obj).__name__}() since it is no longer necessary' - ) - - class _ContextManagerMixin: async def __aenter__(self): await self.acquire() @@ -85,8 +73,8 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundMixin): """ - def __init__(self, *, loop=_marker): - _verify_no_loop(self, loop) + def __init__(self, *, loop=mixins._marker): + super().__init__(loop=loop) self._waiters = None self._locked = False @@ -175,8 +163,8 @@ class Event(mixins._LoopBoundMixin): false. """ - def __init__(self, loop=_marker): - _verify_no_loop(self, loop) + def __init__(self, *, loop=mixins._marker): + super().__init__(loop=loop) self._waiters = collections.deque() self._value = False @@ -238,8 +226,8 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundMixin): A new Lock object is created and used as the underlying lock. """ - def __init__(self, lock=None, *, loop=_marker): - _verify_no_loop(self, loop) + def __init__(self, lock=None, *, loop=mixins._marker): + super().__init__(loop=loop) if lock is None: lock = Lock() elif lock._loop is not self._get_loop(): @@ -358,8 +346,8 @@ class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin): ValueError is raised. """ - def __init__(self, value=1, *, loop=_marker): - _verify_no_loop(self, loop) + def __init__(self, value=1, *, loop=mixins._marker): + super().__init__(loop=loop) if value < 0: raise ValueError("Semaphore initial value must be >= 0") self._value = value @@ -422,10 +410,9 @@ class BoundedSemaphore(Semaphore): above the initial value. """ - def __init__(self, value=1, *, loop=_marker): - _verify_no_loop(self, loop) + def __init__(self, value=1, *, loop=mixins._marker): self._bound_value = value - super().__init__(value) + super().__init__(value, loop=loop) def release(self): if self._value >= self._bound_value: diff --git a/Lib/asyncio/mixins.py b/Lib/asyncio/mixins.py index c6bf97329e92494..650df05ccc93ea6 100644 --- a/Lib/asyncio/mixins.py +++ b/Lib/asyncio/mixins.py @@ -5,10 +5,20 @@ _global_lock = threading.Lock() +# Used as a sentinel for loop parameter +_marker = object() + class _LoopBoundMixin: _loop = None + def __init__(self, *, loop=_marker): + if loop is not _marker: + raise TypeError( + f'As of 3.10, the *loop* parameter was removed from ' + f'{type(self).__name__}() since it is no longer necessary' + ) + def _get_loop(self): loop = events._get_running_loop() diff --git a/Lib/asyncio/queues.py b/Lib/asyncio/queues.py index eb03ff47abaf83c..a87ec8b2158767a 100644 --- a/Lib/asyncio/queues.py +++ b/Lib/asyncio/queues.py @@ -29,7 +29,8 @@ class Queue(mixins._LoopBoundMixin): interrupted between calling qsize() and doing an operation on the Queue. """ - def __init__(self, maxsize=0): + def __init__(self, maxsize=0, *, loop=mixins._marker): + super().__init__(loop=loop) self._maxsize = maxsize # Futures.