What I'm trying to do is to get notified whenever:
- an asyncio Task is created
- anyone awaits on an asyncio Task
The first one is straightforward - use a task factory. For the second one - there are two implementation approaches I could think of:
1) Wrap the task with a new Python object that behaves like a future (enough to fool other code), forward whatever methods necessary to the wrapped task
2) monkey patch await in the original task to notify me, and then forward it to the original __await.
I tried to go for option (2) first because it seems easier. However, I can't get it to work. Attempts to modify await (directly or via setattr) seem to have no effect whatsoever.
Questions:
- Any way to get (2) to work?
- How safe is the (1) approach?
- Is there a "clean" way to wrap a task in a future that I could use?
Here's some code showing what I tried doing.
import asyncio
def patch_task_factory(loop):
prior = loop.get_task_factory()
def factory(loop, coro):
if prior is None:
task = asyncio.Task(coro, loop=loop)
else:
task = prior(loop, coro)
# here we know a task has been created
# but to also get notified when someone waits for it, we need to patch __await__
orig_await = task.__await__
def intercepted_await(self):
print("someone is awaiting this task")
# should work because we're just returning the iterator from the real await
return orig_await(self)
task.__await__ = intercepted_await.__get__(task, asyncio.Task)
return task
loop.set_task_factory(factory)
async def test1():
print("test1!")
return 1
async def test2():
print("test2!")
return await asyncio.create_task(test1())
def main():
loop = asyncio.get_event_loop()
patch_task_factory(loop)
res = asyncio.run(test2())
print(res)
if __name__ == "__main__":
# execute only if run as a script
main()