Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6c23635

Browse files
gh-111085: Fix invalid state handling in TaskGroup and Timeout (#111111)
asyncio.TaskGroup and asyncio.Timeout classes now raise proper RuntimeErrorif they are improperly used.* When they are used without entering the context manager.* When they are used after finishing.* When the context manager is entered more than once (simultaneously or sequentially).* If there is no current task when entering the context manager.They now remain in a consistent state after an exception is thrown,so subsequent operations can be performed correctly (if they are allowed).Co-authored-by: James Hilton-Balfe <gobot1234yt@gmail.com>
1 parentfd60549 commit6c23635

File tree

6 files changed

+120
-9
lines changed

6 files changed

+120
-9
lines changed

‎Lib/asyncio/taskgroups.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,14 @@ def __repr__(self):
5454
asyncdef__aenter__(self):
5555
ifself._entered:
5656
raiseRuntimeError(
57-
f"TaskGroup{self!r} has been already entered")
58-
self._entered=True
59-
57+
f"TaskGroup{self!r} has already been entered")
6058
ifself._loopisNone:
6159
self._loop=events.get_running_loop()
62-
6360
self._parent_task=tasks.current_task(self._loop)
6461
ifself._parent_taskisNone:
6562
raiseRuntimeError(
6663
f'TaskGroup{self!r} cannot determine the parent task')
64+
self._entered=True
6765

6866
returnself
6967

‎Lib/asyncio/timeouts.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ def when(self) -> Optional[float]:
4949

5050
defreschedule(self,when:Optional[float])->None:
5151
"""Reschedule the timeout."""
52-
assertself._stateisnot_State.CREATED
5352
ifself._stateisnot_State.ENTERED:
53+
ifself._stateis_State.CREATED:
54+
raiseRuntimeError("Timeout has not been entered")
5455
raiseRuntimeError(
5556
f"Cannot change state of{self._state.value} Timeout",
5657
)
@@ -82,11 +83,14 @@ def __repr__(self) -> str:
8283
returnf"<Timeout [{self._state.value}]{info_str}>"
8384

8485
asyncdef__aenter__(self)->"Timeout":
86+
ifself._stateisnot_State.CREATED:
87+
raiseRuntimeError("Timeout has already been entered")
88+
task=tasks.current_task()
89+
iftaskisNone:
90+
raiseRuntimeError("Timeout should be used inside a task")
8591
self._state=_State.ENTERED
86-
self._task=tasks.current_task()
92+
self._task=task
8793
self._cancelling=self._task.cancelling()
88-
ifself._taskisNone:
89-
raiseRuntimeError("Timeout should be used inside a task")
9094
self.reschedule(self._when)
9195
returnself
9296

‎Lib/test/test_asyncio/test_taskgroups.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
fromasyncioimporttaskgroups
99
importunittest
1010

11+
fromtest.test_asyncio.utilsimportawait_without_task
12+
1113

1214
# To prevent a warning "test altered the execution environment"
1315
deftearDownModule():
@@ -779,6 +781,49 @@ async def main():
779781

780782
awaitasyncio.create_task(main())
781783

784+
asyncdeftest_taskgroup_already_entered(self):
785+
tg=taskgroups.TaskGroup()
786+
asyncwithtg:
787+
withself.assertRaisesRegex(RuntimeError,"has already been entered"):
788+
asyncwithtg:
789+
pass
790+
791+
asyncdeftest_taskgroup_double_enter(self):
792+
tg=taskgroups.TaskGroup()
793+
asyncwithtg:
794+
pass
795+
withself.assertRaisesRegex(RuntimeError,"has already been entered"):
796+
asyncwithtg:
797+
pass
798+
799+
asyncdeftest_taskgroup_finished(self):
800+
tg=taskgroups.TaskGroup()
801+
asyncwithtg:
802+
pass
803+
coro=asyncio.sleep(0)
804+
withself.assertRaisesRegex(RuntimeError,"is finished"):
805+
tg.create_task(coro)
806+
# We still have to await coro to avoid a warning
807+
awaitcoro
808+
809+
asyncdeftest_taskgroup_not_entered(self):
810+
tg=taskgroups.TaskGroup()
811+
coro=asyncio.sleep(0)
812+
withself.assertRaisesRegex(RuntimeError,"has not been entered"):
813+
tg.create_task(coro)
814+
# We still have to await coro to avoid a warning
815+
awaitcoro
816+
817+
asyncdeftest_taskgroup_without_parent_task(self):
818+
tg=taskgroups.TaskGroup()
819+
withself.assertRaisesRegex(RuntimeError,"parent task"):
820+
awaitawait_without_task(tg.__aenter__())
821+
coro=asyncio.sleep(0)
822+
withself.assertRaisesRegex(RuntimeError,"has not been entered"):
823+
tg.create_task(coro)
824+
# We still have to await coro to avoid a warning
825+
awaitcoro
826+
782827

783828
if__name__=="__main__":
784829
unittest.main()

‎Lib/test/test_asyncio/test_timeouts.py‎

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
importasyncio
77

8+
fromtest.test_asyncio.utilsimportawait_without_task
9+
810

911
deftearDownModule():
1012
asyncio.set_event_loop_policy(None)
1113

12-
1314
classTimeoutTests(unittest.IsolatedAsyncioTestCase):
1415

1516
asyncdeftest_timeout_basic(self):
@@ -257,6 +258,51 @@ async def test_timeout_exception_cause (self):
257258
cause=exc.exception.__cause__
258259
assertisinstance(cause,asyncio.CancelledError)
259260

261+
asyncdeftest_timeout_already_entered(self):
262+
asyncwithasyncio.timeout(0.01)ascm:
263+
withself.assertRaisesRegex(RuntimeError,"has already been entered"):
264+
asyncwithcm:
265+
pass
266+
267+
asyncdeftest_timeout_double_enter(self):
268+
asyncwithasyncio.timeout(0.01)ascm:
269+
pass
270+
withself.assertRaisesRegex(RuntimeError,"has already been entered"):
271+
asyncwithcm:
272+
pass
273+
274+
asyncdeftest_timeout_finished(self):
275+
asyncwithasyncio.timeout(0.01)ascm:
276+
pass
277+
withself.assertRaisesRegex(RuntimeError,"finished"):
278+
cm.reschedule(0.02)
279+
280+
asyncdeftest_timeout_expired(self):
281+
withself.assertRaises(TimeoutError):
282+
asyncwithasyncio.timeout(0.01)ascm:
283+
awaitasyncio.sleep(1)
284+
withself.assertRaisesRegex(RuntimeError,"expired"):
285+
cm.reschedule(0.02)
286+
287+
asyncdeftest_timeout_expiring(self):
288+
asyncwithasyncio.timeout(0.01)ascm:
289+
withself.assertRaises(asyncio.CancelledError):
290+
awaitasyncio.sleep(1)
291+
withself.assertRaisesRegex(RuntimeError,"expiring"):
292+
cm.reschedule(0.02)
293+
294+
asyncdeftest_timeout_not_entered(self):
295+
cm=asyncio.timeout(0.01)
296+
withself.assertRaisesRegex(RuntimeError,"has not been entered"):
297+
cm.reschedule(0.02)
298+
299+
asyncdeftest_timeout_without_task(self):
300+
cm=asyncio.timeout(0.01)
301+
withself.assertRaisesRegex(RuntimeError,"task"):
302+
awaitawait_without_task(cm.__aenter__())
303+
withself.assertRaisesRegex(RuntimeError,"has not been entered"):
304+
cm.reschedule(0.02)
305+
260306

261307
if__name__=='__main__':
262308
unittest.main()

‎Lib/test/test_asyncio/utils.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,3 +612,18 @@ def mock_nonblocking_socket(proto=socket.IPPROTO_TCP, type=socket.SOCK_STREAM,
612612
sock.family=family
613613
sock.gettimeout.return_value=0.0
614614
returnsock
615+
616+
617+
asyncdefawait_without_task(coro):
618+
exc=None
619+
deffunc():
620+
try:
621+
for_incoro.__await__():
622+
pass
623+
exceptBaseExceptionaserr:
624+
nonlocalexc
625+
exc=err
626+
asyncio.get_running_loop().call_soon(func)
627+
awaitasyncio.sleep(0)
628+
ifexcisnotNone:
629+
raiseexc
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix invalid state handling in:class:`asyncio.TaskGroup` and
2+
:class:`asyncio.Timeout`. They now raise proper RuntimeError if they are
3+
improperly used and are left in consistent state after this.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp