-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
asyncio.create_task() documentation should mention user needs to keep reference to the task #88831
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
asyncio will only keep weak references to alive tasks (in I would suggest adding the following paragraph to Python only keeps weak references to the scheduled tasks. To avoid the task being destroyed by the garbage collector while still pending, a reference to it should be kept until the task is done. And maybe an example in case a user wants something "fire and forget"? running_tasks = set()
# [...]
task = asyncio.create_task(some_background_function())
running_tasks.add(task)
task.add_done_callback(lambda t: running_tasks.remove(t)) The same applies to ensure_future as it now uses create_task, so maybe a "See create_task()". |
@bernat and ncoghlan, please see if the wording I have used in the linked PR helps to clarify this. |
Is there a way to reproduce this issue? I run the following code in Python 3.9 and it works as expected (prints "xyz" twice). import asyncio
import gc
async def xyz():
print("xyz")
event_loop = asyncio.get_event_loop()
event_loop.create_task(xyz())
t = event_loop.create_task(xyz())
del t
gc.collect()
event_loop.stop()
event_loop.run_forever() |
Hummm, I have a hard time finding a short example when |
I just found this PR when a task of mine spontaneously crashed with a "Task was destroyed but it is pending" in the middle of program execution. I think the warning should be added to I think it would be a good idea to add the fire-and-forget example that @bernat gave. At the moment, stackoverflow is full of suggestions to just use |
Hi everybody, I noticed the warning about "you have to keep a reference to the task" at the python docs, but this triggered only more questions. Why should I need to keep a reference when the event loop also must have a reference to run the task? I even asked about it at Stack Overflow (see here) and only found this github issue later. I agree with @alexhartl at #88831 (comment) that the workaround using "Important Save a reference to the result of this function, to avoid a task disappearing mid execution. The event loop only keeps weak references to task objects." Those two things together would have saved me some head-scratching. Maybe for a later release of python (3.11?) an addition to the API for 'fire and forget tasks' would be nice, something like Thanks for your great work! |
Thanks for clarifying, feel free to open a PR if you have time with the desired wording. |
I would like to contribute to the documentation of Thanks! |
@agrommek, Open on the main branch, the change can be backported to older branches if required. |
…ces to tasks are needed (GH-93258) Co-authored-by: Łukasz Langa <lukasz@langa.pl>
…eferences to tasks are needed (pythonGH-93258) Co-authored-by: Łukasz Langa <lukasz@langa.pl> (cherry picked from commit 75ceae0) Co-authored-by: Andreas Grommek <76997441+agrommek@users.noreply.github.com>
…eferences to tasks are needed (pythonGH-93258) Co-authored-by: Łukasz Langa <lukasz@langa.pl> (cherry picked from commit 75ceae0) Co-authored-by: Andreas Grommek <76997441+agrommek@users.noreply.github.com>
The reason was mentioned above. |
I have just discovered this, after days hunting a bug where my tasks where disappearing. What is unclear to me now is:
|
What about |
This clears up log spam for regtest tests. related: - https://bugs.python.org/issue44665 - python/cpython#88831 - https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/ - python/cpython#91887 (comment) - "Task was destroyed but it is pending!" Perhaps we should inspect all our usages of - asyncio.create_task - loop.create_task - asyncio.ensure_future - asyncio.run_coroutine_threadsafe ? Example log for running a regtest test: ``` $ python3 -m unittest electrum.tests.regtest.TestLightningAB.test_collaborative_close ***** test_collaborative_close ****** initializing alice --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-2' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future finished result=0> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () [--- SNIP --- more of the same --- SNIP ---] --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-31' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/lib/python3.10/asyncio/futures.py:385, Task.task_wakeup()]> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () true true true true funding alice ```
This clears up log spam for regtest tests. related: - https://bugs.python.org/issue44665 - python/cpython#88831 - https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/ - python/cpython#91887 (comment) - "Task was destroyed but it is pending!" Perhaps we should inspect all our usages of - asyncio.create_task - loop.create_task - asyncio.ensure_future - asyncio.run_coroutine_threadsafe ? Example log for running a regtest test: ``` $ python3 -m unittest electrum.tests.regtest.TestLightningAB.test_collaborative_close ***** test_collaborative_close ****** initializing alice --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-2' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future finished result=0> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () [--- SNIP --- more of the same --- SNIP ---] --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-31' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/lib/python3.10/asyncio/futures.py:385, Task.task_wakeup()]> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () true true true true funding alice ```
Here is my answer for anyone wondering how to reproduce this issue (as their code works just fine without keeping a reference to the tasks). I tried to explain what I understood: |
This clears up log spam for regtest tests. related: - https://bugs.python.org/issue44665 - python/cpython#88831 - https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/ - python/cpython#91887 (comment) - "Task was destroyed but it is pending!" Perhaps we should inspect all our usages of - asyncio.create_task - loop.create_task - asyncio.ensure_future - asyncio.run_coroutine_threadsafe ? Example log for running a regtest test: ``` $ python3 -m unittest electrum.tests.regtest.TestLightningAB.test_collaborative_close ***** test_collaborative_close ****** initializing alice --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-2' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future finished result=0> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () [--- SNIP --- more of the same --- SNIP ---] --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-31' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/lib/python3.10/asyncio/futures.py:385, Task.task_wakeup()]> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () true true true true funding alice ```
This clears up log spam for regtest tests. related: - https://bugs.python.org/issue44665 - python/cpython#88831 - https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/ - python/cpython#91887 (comment) - "Task was destroyed but it is pending!" Perhaps we should inspect all our usages of - asyncio.create_task - loop.create_task - asyncio.ensure_future - asyncio.run_coroutine_threadsafe ? Example log for running a regtest test: ``` $ python3 -m unittest electrum.tests.regtest.TestLightningAB.test_collaborative_close ***** test_collaborative_close ****** initializing alice --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-2' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future finished result=0> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () [--- SNIP --- more of the same --- SNIP ---] --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-31' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/lib/python3.10/asyncio/futures.py:385, Task.task_wakeup()]> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () true true true true funding alice ```
This clears up log spam for regtest tests. related: - https://bugs.python.org/issue44665 - python/cpython#88831 - https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/ - python/cpython#91887 (comment) - "Task was destroyed but it is pending!" Perhaps we should inspect all our usages of - asyncio.create_task - loop.create_task - asyncio.ensure_future - asyncio.run_coroutine_threadsafe ? Example log for running a regtest test: ``` $ python3 -m unittest electrum.tests.regtest.TestLightningAB.test_collaborative_close ***** test_collaborative_close ****** initializing alice --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-2' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future finished result=0> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () [--- SNIP --- more of the same --- SNIP ---] --- Logging error --- Traceback (most recent call last): File "/usr/lib/python3.10/logging/__init__.py", line 1100, in emit msg = self.format(record) File "/usr/lib/python3.10/logging/__init__.py", line 943, in format return fmt.format(record) File "/home/user/wspace/electrum/electrum/logging.py", line 44, in format record = copy.copy(record) # avoid mutating arg File "/usr/lib/python3.10/copy.py", line 92, in copy rv = reductor(4) ImportError: sys.meta_path is None, Python is likely shutting down Call stack: File "/usr/lib/python3.10/asyncio/base_events.py", line 1781, in call_exception_handler self._exception_handler(self, context) File "/home/user/wspace/electrum/electrum/util.py", line 1535, in on_exception loop.default_exception_handler(context) File "/usr/lib/python3.10/asyncio/base_events.py", line 1744, in default_exception_handler logger.error('\n'.join(log_lines), exc_info=exc_info) Message: "Task was destroyed but it is pending!\ntask: <Task pending name='Task-31' coro=<Abstract_Wallet.on_event_adb_set_up_to_date() running at /home/user/wspace/electrum/electrum/wallet.py:485> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/lib/python3.10/asyncio/futures.py:385, Task.task_wakeup()]> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:392]>" Arguments: () true true true true funding alice ```
@vincentbernat just to clarify, since I'm not that familiar with the intricacies of Python's garbage collector. Is this enough (in the context of this issue) to keep a reference of the task like this? async def coro():
task1 = asyncio.create_task(...)
task2 = asyncio.create_task(...)
task3 = asyncio.create_task(...)
task4 = asyncio.create_task(...) Or is it really needed to keep them in a collection? If so, can the collection be declared inside the coroutine? Or does it need to be outside to be a "strong" reference? async def coro():
running_tasks = set()
task1 = asyncio.create_task(...)
running_tasks.add(task1)
task2 = asyncio.create_task(...)
running_tasks.add(task2)
task3 = asyncio.create_task(...)
running_tasks.add(task3)
task4 = asyncio.create_task(...)
running_tasks.add(task4) vs. running_tasks = set()
async def coro():
task1 = asyncio.create_task(...)
running_tasks.add(task1)
task2 = asyncio.create_task(...)
running_tasks.add(task2)
task3 = asyncio.create_task(...)
running_tasks.add(task3)
task4 = asyncio.create_task(...)
running_tasks.add(task4) Just trying to keep the code as simple as possible. |
Only the last one will work. The two other ones don't keep a reference on the tasks after the coro() function ends. |
But when the coro() function ends, all the tasks created inside coro() are done, so I don't need to keep the references anymore, right? Maybe I should have clarified that I call the So like: async def coro():
task1 = asyncio.create_task(...)
task2 = asyncio.create_task(...)
task3 = asyncio.create_task(...)
task4 = asyncio.create_task(...)
await task1
await task2
await task3
await task4
# all the tasks are done and coro() ends In this case, would it work? Do I even need to keep the references async def coro():
await asyncio.create_task(...)
await asyncio.create_task(...)
await asyncio.create_task(...)
await asyncio.create_task(...)
# all the tasks are done and coro() ends Thanks for the help. Editing this post to avoid creating more noise:
Agree but the documentation was updated following this issue and the wording is confusing. As you can see in the previous comments and in mine, it raises a lot of questions. The doc suggests to keep references of the tasks in any case, while apparently is not always necessary (like in my example). I appreciate the help anyway. Thanks. |
I don't think this issue should be a support forum. But yes, both ways are fine (but they don't do the same thing). |
The task returned by `asyncio.create_task` should not be discarded. See python/cpython#88831
|
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: