You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Crash in asyncio when printing a task object at teardown #96232
If a task object is still alive at interpreter teardown, something can try to print it. This will end in _asyncio.Future._repr_info that in turn calls:
but asyncio_future_repr_info_func at that point can be NULL because module_free of _asynciomodule.c has been executed already, called by Py_FinalizeEx -> finalize_modules -> _PyInterpreterState_ClearModules -> ... -> module_dealloc.
The crash happens in the last _PyGC_CollectNoFail of finalize_modules so somehow the task survived the first collection and then was garbage in the second one.
I don;t have a simple repro for now, but you can get the failure by cloning https://github.com/Textualize/textual checking commit 5fe47da058564b72e5081a5658c054f55a5d85cd, installing the project and test deps and running pytest.
Would be great if someone can work on a simple reproducer. We need a task/future object that survives the first collection at shutdown and something tries to print it in the second. In the reproducer, what tries to remove it is this code:
frame #20: 0x000000010037b9ec python`deque_remove(deque=0x0000000106653e50, value=0x00000001062e8d10) at _collectionsmodule.c:1260:9
1257 }
1258 }
1259 if (i == n) {
-> 1260 PyErr_Format(PyExc_ValueError, "%R is not in deque", value);
1261 return NULL;
1262 }
1263 rv = deque_del_item(deque, I);
that value is the task object. That happens while calling gen_close_iter on a coroutine object MessagePump._process_messages object.
@pablogsal Which exact Python version and commit were you using? (The line numbers in the stack trace seem slightly off.)
Maybe this was already obvious to everybody on this issue, but from the stack trace it looks like a bunch of nested await calls are finally being terminated.
There are several await calls in MessagePump._process_messages (I didn't find this code -- Pablo mentioned it, it's in the textual package) and the last one is inside a try-finally whose finally block does more await calls (dispatching "on_idle" events, which is a textual thing).
It would seem that something is finally cancelling this stack of await calls -- the stack trace shows that it's throwing a GeneratorExit into the innermost coroutine. I suspect that this might be calling asyncio.Queue.get() (but I'm not sure).
I also note that the message pump code uses a WeakSet of tasks, and in close_messages() there is this code:
Would it be possible that this loop doesn't cancel all the tasks because some tasks are GC'ed elsewhere? We know that keeping a weak reference to a task is not enough to keep it alive, GC can kill it without closing it. This mechanism is only used for timers created by textual/timer.py.
Someone should probably investigate that timer class more (since there may be a clue to a simpler repro here), but I'm tired and I need to go to bed.
Oh darn, looks like I was looking at the HEAD of the main branch of textualize, instead of the commit 5fe47da058564b72e5081a5658c054f55a5d85cd as Pablo said. The finally block in _process_messages is even more complex there. :-(
PR #99122 is merged now so this shouldn't happen on 3.12+. Since nobody was able to create a reproducer for this, I propose to close this as won't fix.
Activity
pablogsal commentedon Aug 24, 2022
CC: @graingert
pablogsal commentedon Aug 24, 2022
Backtrace of the crash:
In debug mode we get an assert failing:
And here is the problem already-cleaned object:
pablogsal commentedon Aug 24, 2022
This is enough to fix the segfault, but is a very incomplete fix:
Technically the crash can happen with any method.
pablogsal commentedon Aug 24, 2022
Would be great if someone can work on a simple reproducer. We need a task/future object that survives the first collection at shutdown and something tries to print it in the second. In the reproducer, what tries to remove it is this code:
that
valueis the task object. That happens while callinggen_close_iteron acoroutine object MessagePump._process_messagesobject.pablogsal commentedon Aug 24, 2022
CC: @kumaraditya303 in case you want a challenge :)
graingert commentedon Aug 24, 2022
I started messing around triggering Task GC in this reproducer, I'm not sure if it will help here
#94972 (comment)
gvanrossum commentedon Aug 29, 2022
@pablogsal Which exact Python version and commit were you using? (The line numbers in the stack trace seem slightly off.)
Maybe this was already obvious to everybody on this issue, but from the stack trace it looks like a bunch of nested
awaitcalls are finally being terminated.There are several
awaitcalls inMessagePump._process_messages(I didn't find this code -- Pablo mentioned it, it's in thetextualpackage) and the last one is inside atry-finallywhosefinallyblock does moreawaitcalls (dispatching"on_idle"events, which is atextualthing).It would seem that something is finally cancelling this stack of
awaitcalls -- the stack trace shows that it's throwing aGeneratorExitinto the innermost coroutine. I suspect that this might be callingasyncio.Queue.get()(but I'm not sure).I also note that the message pump code uses a
WeakSetof tasks, and inclose_messages()there is this code:Would it be possible that this loop doesn't cancel all the tasks because some tasks are GC'ed elsewhere? We know that keeping a weak reference to a task is not enough to keep it alive, GC can kill it without closing it. This mechanism is only used for timers created by
textual/timer.py.Someone should probably investigate that timer class more (since there may be a clue to a simpler repro here), but I'm tired and I need to go to bed.
gvanrossum commentedon Aug 29, 2022
Oh darn, looks like I was looking at the HEAD of the main branch of textualize, instead of the commit 5fe47da058564b72e5081a5658c054f55a5d85cd as Pablo said. The
finallyblock in_process_messagesis even more complex there. :-(kumaraditya303 commentedon Aug 30, 2022
I still haven't managed to create a smaller reproducer but hopefully this will be fixed once #91375 is fixed.
kumaraditya303 commentedon Nov 29, 2022
PR #99122 is merged now so this shouldn't happen on 3.12+. Since nobody was able to create a reproducer for this, I propose to close this as won't fix.
gvanrossum commentedon Nov 29, 2022
Agreed.