-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
asyncio.create_task() documentation should mention user needs to keep reference to the task #88831
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 <[email protected]>
…eferences to tasks are needed (pythonGH-93258) Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 75ceae0) Co-authored-by: Andreas Grommek <[email protected]>
…eferences to tasks are needed (pythonGH-93258) Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 75ceae0) Co-authored-by: Andreas Grommek <[email protected]>
…ces to tasks are needed (GH-93258) (GH-93566) Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 75ceae0) Co-authored-by: Andreas Grommek <[email protected]>
…ces to tasks are needed (GH-93258) (GH-93567) Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 75ceae0) Co-authored-by: Andreas Grommek <[email protected]>
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: