Skip to content

Commit b9127dd

Browse files
authored
bpo-42392: Improve removal of *loop* parameter in asyncio primitives (GH-23499)
* Update code after merge review from 1st1 * Use a sentinel approach for loop parameter Remove unnecessary _get_running_loop patching * Use more clear function name (_verify_parameter_is_marker -> _verify_no_loop) * Add init method to _LoopBoundMixin to check that loop param wasn't used
1 parent 7301979 commit b9127dd

File tree

5 files changed

+46
-35
lines changed

5 files changed

+46
-35
lines changed

Lib/asyncio/locks.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async def __aexit__(self, exc_type, exc, tb):
1919
self.release()
2020

2121

22-
class Lock(_ContextManagerMixin, mixins._LoopBoundedMixin):
22+
class Lock(_ContextManagerMixin, mixins._LoopBoundMixin):
2323
"""Primitive lock objects.
2424
2525
A primitive lock is a synchronization primitive that is not owned
@@ -73,7 +73,8 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundedMixin):
7373
7474
"""
7575

76-
def __init__(self):
76+
def __init__(self, *, loop=mixins._marker):
77+
super().__init__(loop=loop)
7778
self._waiters = None
7879
self._locked = False
7980

@@ -153,7 +154,7 @@ def _wake_up_first(self):
153154
fut.set_result(True)
154155

155156

156-
class Event(mixins._LoopBoundedMixin):
157+
class Event(mixins._LoopBoundMixin):
157158
"""Asynchronous equivalent to threading.Event.
158159
159160
Class implementing event objects. An event manages a flag that can be set
@@ -162,7 +163,8 @@ class Event(mixins._LoopBoundedMixin):
162163
false.
163164
"""
164165

165-
def __init__(self):
166+
def __init__(self, *, loop=mixins._marker):
167+
super().__init__(loop=loop)
166168
self._waiters = collections.deque()
167169
self._value = False
168170

@@ -214,7 +216,7 @@ async def wait(self):
214216
self._waiters.remove(fut)
215217

216218

217-
class Condition(_ContextManagerMixin, mixins._LoopBoundedMixin):
219+
class Condition(_ContextManagerMixin, mixins._LoopBoundMixin):
218220
"""Asynchronous equivalent to threading.Condition.
219221
220222
This class implements condition variable objects. A condition variable
@@ -224,7 +226,8 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundedMixin):
224226
A new Lock object is created and used as the underlying lock.
225227
"""
226228

227-
def __init__(self, lock=None):
229+
def __init__(self, lock=None, *, loop=mixins._marker):
230+
super().__init__(loop=loop)
228231
if lock is None:
229232
lock = Lock()
230233
elif lock._loop is not self._get_loop():
@@ -328,7 +331,7 @@ def notify_all(self):
328331
self.notify(len(self._waiters))
329332

330333

331-
class Semaphore(_ContextManagerMixin, mixins._LoopBoundedMixin):
334+
class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin):
332335
"""A Semaphore implementation.
333336
334337
A semaphore manages an internal counter which is decremented by each
@@ -343,7 +346,8 @@ class Semaphore(_ContextManagerMixin, mixins._LoopBoundedMixin):
343346
ValueError is raised.
344347
"""
345348

346-
def __init__(self, value=1):
349+
def __init__(self, value=1, *, loop=mixins._marker):
350+
super().__init__(loop=loop)
347351
if value < 0:
348352
raise ValueError("Semaphore initial value must be >= 0")
349353
self._value = value
@@ -406,9 +410,9 @@ class BoundedSemaphore(Semaphore):
406410
above the initial value.
407411
"""
408412

409-
def __init__(self, value=1):
413+
def __init__(self, value=1, *, loop=mixins._marker):
410414
self._bound_value = value
411-
super().__init__(value)
415+
super().__init__(value, loop=loop)
412416

413417
def release(self):
414418
if self._value >= self._bound_value:

Lib/asyncio/mixins.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@
55

66
_global_lock = threading.Lock()
77

8+
# Used as a sentinel for loop parameter
9+
_marker = object()
810

9-
class _LoopBoundedMixin:
11+
12+
class _LoopBoundMixin:
1013
_loop = None
1114

15+
def __init__(self, *, loop=_marker):
16+
if loop is not _marker:
17+
raise TypeError(
18+
f'As of 3.10, the *loop* parameter was removed from '
19+
f'{type(self).__name__}() since it is no longer necessary'
20+
)
21+
1222
def _get_loop(self):
1323
loop = events._get_running_loop()
1424

@@ -17,5 +27,5 @@ def _get_loop(self):
1727
if self._loop is None:
1828
self._loop = loop
1929
if loop is not self._loop:
20-
raise RuntimeError(f'{type(self).__name__} have already bounded to another loop')
30+
raise RuntimeError(f'{self!r} is bound to a different event loop')
2131
return loop

Lib/asyncio/queues.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class QueueFull(Exception):
1717
pass
1818

1919

20-
class Queue(mixins._LoopBoundedMixin):
20+
class Queue(mixins._LoopBoundMixin):
2121
"""A queue, useful for coordinating producer and consumer coroutines.
2222
2323
If maxsize is less than or equal to zero, the queue size is infinite. If it
@@ -29,7 +29,8 @@ class Queue(mixins._LoopBoundedMixin):
2929
interrupted between calling qsize() and doing an operation on the Queue.
3030
"""
3131

32-
def __init__(self, maxsize=0):
32+
def __init__(self, maxsize=0, *, loop=mixins._marker):
33+
super().__init__(loop=loop)
3334
self._maxsize = maxsize
3435

3536
# Futures.

Lib/test/test_asyncio/test_locks.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ def acquire_lock():
5151

5252
self.assertFalse(lock.locked())
5353

54+
def test_lock_doesnt_accept_loop_parameter(self):
55+
primitives_cls = [
56+
asyncio.Lock,
57+
asyncio.Condition,
58+
asyncio.Event,
59+
asyncio.Semaphore,
60+
asyncio.BoundedSemaphore,
61+
]
62+
63+
for cls in primitives_cls:
64+
with self.assertRaisesRegex(
65+
TypeError,
66+
rf'As of 3.10, the \*loop\* parameter was removed from '
67+
rf'{cls.__name__}\(\) since it is no longer necessary'
68+
):
69+
cls(loop=self.loop)
70+
5471
def test_lock_by_with_statement(self):
5572
loop = asyncio.new_event_loop() # don't use TestLoop quirks
5673
self.set_event_loop(loop)

Lib/test/test_asyncio/utils.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -541,31 +541,10 @@ def new_test_loop(self, gen=None):
541541
self.set_event_loop(loop)
542542
return loop
543543

544-
def unpatch_get_running_loop(self):
545-
events._get_running_loop = self._get_running_loop
546-
547544
def setUp(self):
548-
self._get_running_loop = events._get_running_loop
549-
550-
def _get_running_loop():
551-
frame = sys._getframe(1)
552-
553-
if frame.f_globals['__name__'] == 'asyncio.mixins':
554-
# When we called from LoopBoundedMixin we should
555-
# fallback to default implementation of get_running_loop
556-
try:
557-
return events.get_running_loop()
558-
except RuntimeError:
559-
return None
560-
561-
return None
562-
563-
events._get_running_loop = _get_running_loop
564545
self._thread_cleanup = threading_helper.threading_setup()
565546

566547
def tearDown(self):
567-
self.unpatch_get_running_loop()
568-
569548
events.set_event_loop(None)
570549

571550
# Detect CPython bug #23353: ensure that yield/yield-from is not used

0 commit comments

Comments
 (0)