From 487c77a96504c0178581a7a4bb7e9cd66783548f Mon Sep 17 00:00:00 2001 From: bd Date: Mon, 21 Jul 2025 13:25:56 -0600 Subject: Update comments reflecting on asyncio.gather and task groups --- aergia/aergia.py | 3 +- t/test_functionality.py | 79 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/aergia/aergia.py b/aergia/aergia.py index 83cf0fa..b10c82b 100755 --- a/aergia/aergia.py +++ b/aergia/aergia.py @@ -251,7 +251,6 @@ class Aergia(object): ret = None while curr: frame = getattr(curr, 'cr_frame', None) - # print(frame) if frame and Aergia.should_trace(frame.f_code.co_filename): ret = frame curr = getattr(curr, 'cr_await', None) @@ -260,7 +259,7 @@ class Aergia(object): @staticmethod def should_trace(filename): '''Returns FALSE if filename is uninteresting to the user. - Don't depend on this.''' + Don't depend on this. It's good enough for testing.''' # FIXME Assume GuixSD. Makes filtering easy if '/gnu/store' in filename: return False diff --git a/t/test_functionality.py b/t/test_functionality.py index 63863cf..75f0137 100644 --- a/t/test_functionality.py +++ b/t/test_functionality.py @@ -3,6 +3,10 @@ import asyncio import threading +class TerminateTaskGroup(Exception): + '''Exception raised to terminate a task group.''' + + class BasicUsage(utils.AergiaUnitTestCase): def test_asyncless(self): @@ -55,7 +59,10 @@ class BasicUsage(utils.AergiaUnitTestCase): self.assertFuncContains('b', [self.expected_samples(delay * 3)], samples) - # TODO samples from gather all execution time, should we trace this? + # the gather function is technically waiting for all tasks to finish. + # This might be seen as unintuitive though I don't want to bias the + # results by adding logic to add artificial consistency. + # profiling does not mean obscuring implementation details. self.assertFuncContains('a', [self.expected_samples(delay)], samples) def test_subthread_task(self): @@ -75,7 +82,7 @@ class BasicUsage(utils.AergiaUnitTestCase): self.assertFuncContains('c', [], samples) self.assertFuncContains('b', [self.expected_samples(delay * 3)], samples) - # TODO samples from gather all execution time, should we trace this? + # see comment on `test_simultaneous_tasks'. self.assertFuncContains('a', [self.expected_samples(delay)], samples) def test_eager_task(self): @@ -92,6 +99,31 @@ class BasicUsage(utils.AergiaUnitTestCase): samples = self.Aergia.get_samples() self.assertFuncContains('a', [self.expected_samples(delay)], samples) + def test_async_eager_scheduled_woven(self): + d1 = 0.2 + d2 = 0.3 + d3 = 0.2 + d4 = 0.1 + + async def a(): + await asyncio.sleep(d1) + proc = await asyncio.create_subprocess_shell(f'sleep {d2}') + await proc.communicate() + await asyncio.sleep(d3) + proc = await asyncio.create_subprocess_shell(f'sleep {d4}') + await proc.communicate() + + self.Aergia.start() + asyncio.run(a()) + self.Aergia.stop() + + samples = self.Aergia.get_samples() + self.assertFuncContains('a', [self.expected_samples(d1), + self.expected_samples(d2), + self.expected_samples(d3), + self.expected_samples(d4), + ], samples) + def test_async_generator(self): delay = 0.2 num_times = 10 @@ -111,8 +143,8 @@ class BasicUsage(utils.AergiaUnitTestCase): self.Aergia.stop() samples = self.Aergia.get_samples() - # TODO can we make these results more intuitive? - # async generators also do not work at all with yappi. + # TODO I do not think async generators report correctly. + # also does not work at all with yappi. # doing so would be unique to this profiler. # self.assertFuncContains('b', @@ -142,8 +174,8 @@ class BasicUsage(utils.AergiaUnitTestCase): self.Aergia.stop() samples = self.Aergia.get_samples() - # TODO can we make these results more intuitive? - # async generators also do not work at all with yappi. + # TODO I do not think async generators report correctly. + # also does not work at all with yappi. # doing so would be unique to this profiler. self.assertFuncContains('b', [], samples) @@ -192,28 +224,32 @@ class BasicUsage(utils.AergiaUnitTestCase): self.assertFuncContains('d', [self.expected_samples(d1)], samples) self.assertFuncContains('c', [self.expected_samples(d2)], samples) self.assertFuncContains('b', [self.expected_samples(d3)], samples) - # TODO where does this come from? + # the task group is technically waiting for all tasks to finish. + # same as task.gather + # This might be seen as unintuitive, (especially considering how + # the next test works), though I don't want to bias the results + # by adding logic to add artificial consistency. + # Both are reporting correctly. self.assertFuncContains('a', [self.expected_samples(d2)], samples) def test_task_groups_cancel(self): d1 = 0.1 - d2 = 0.2 - d3 = 0.3 - async def d(): await asyncio.sleep(d1) + d2 = 0.3 + d3 = 0.2 + async def d(): raise TerminateTaskGroup - async def c(): - await asyncio.sleep(d2) - 1 / 0 # crash + async def c(): await asyncio.sleep(d1) - async def b(): await asyncio.sleep(d3) + async def b(): await asyncio.sleep(d2) async def a(): try: async with asyncio.TaskGroup() as tg: - tg.create_task(d()) tg.create_task(c()) tg.create_task(b()) - except: + await asyncio.sleep(d3) + tg.create_task(d()) + except* TerminateTaskGroup: pass self.Aergia.start() @@ -222,8 +258,9 @@ class BasicUsage(utils.AergiaUnitTestCase): samples = self.Aergia.get_samples() - self.assertFuncContains('d', [self.expected_samples(d1)], samples) - self.assertFuncContains('c', [self.expected_samples(d2)], samples) - self.assertFuncContains('b', [self.expected_samples(d2)], samples) - # TODO where does this come from? - self.assertFuncContains('a', [self.expected_samples(d2)], samples) + self.assertFuncContains('d', [], samples) + self.assertFuncContains('c', [self.expected_samples(d1)], samples) + self.assertFuncContains('b', [self.expected_samples(d3)], samples) + # this time is attached to the sleep call itself. Aergia's print + # function would confirm this! + self.assertFuncContains('a', [self.expected_samples(d3)], samples) -- cgit v1.2.3