-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
trip_signal() gets NULL tstate on Windows on CTRL+C #84263
Comments
While trying to make sense of some static analysis warnings for the Windows console IO module, I Ctrl+C'd in the middle of an intentionally absurd __repr__ output, and on proceeding in the debugger (which treated it as an exception), I immediately hit the assertion right here: /* Get the Python thread state using PyGILState API, since
_PyThreadState_GET() returns NULL if the GIL is released.
For example, signal.raise_signal() releases the GIL. */
PyThreadState *tstate = PyGILState_GetThisThreadState();
assert(tstate != NULL); ...With the stacktrace:
...I'm not entirely sure why this happened, but I can tell a few things. _PyRuntime.gilstate.autoInterpreterState is NOT null, in fact the gilstate object is as displayed in my watch window: - _PyRuntime.gilstate {check_enabled=1 tstate_current={_value=0 } getframe=0x79e3a570 {python39_d.dll!threadstate_getframe(_ts *)} ...} _gilstate_runtime_state
check_enabled 1 int
+ tstate_current {_value=0 } _Py_atomic_address
getframe 0x79e3a570 {python39_d.dll!threadstate_getframe(_ts *)} _frame *(*)(_ts *)
- autoInterpreterState 0x00e5eff8 {next=0x00000000 <NULL> tstate_head=0x00e601c0 {prev=0x00000000 <NULL> next=0x00000000 <NULL> ...} ...} _is *
+ next 0x00000000 <NULL> _is *
+ tstate_head 0x00e601c0 {prev=0x00000000 <NULL> next=0x00000000 <NULL> interp=0x00e5eff8 {next=0x00000000 <NULL> ...} ...} _ts *
+ runtime 0x7a0e2118 {python39_d.dll!pyruntimestate _PyRuntime} {preinitializing=0 preinitialized=1 core_initialized=...} pyruntimestate *
id 0 __int64
id_refcount -1 __int64
requires_idref 0 int
id_mutex 0x00000000 void *
finalizing 0 int
+ ceval {tracing_possible=0 eval_breaker={_value=0 } pending={finishing=0 lock=0x00e59390 calls_to_do={_value=...} ...} } _ceval_state
+ gc {trash_delete_later=0x00000000 <NULL> trash_delete_nesting=0 enabled=1 ...} _gc_runtime_state
+ modules 0x00bf1228 {ob_refcnt=3 ob_type=0x7a0b1178 {python39_d.dll!_typeobject PyDict_Type} {ob_base={ob_base=...} ...} } _object *
+ modules_by_index 0x00750058 {ob_refcnt=1 ob_type=0x7a0b8210 {python39_d.dll!_typeobject PyList_Type} {ob_base={ob_base=...} ...} } _object *
+ sysdict 0x00bf1298 {ob_refcnt=2 ob_type=0x7a0b1178 {python39_d.dll!_typeobject PyDict_Type} {ob_base={ob_base=...} ...} } _object *
+ builtins 0x00bf1f48 {ob_refcnt=88 ob_type=0x7a0b1178 {python39_d.dll!_typeobject PyDict_Type} {ob_base={ob_base=...} ...} } _object *
+ importlib 0x00c0df60 {ob_refcnt=28 ob_type=0x7a0b92d0 {python39_d.dll!_typeobject PyModule_Type} {ob_base={ob_base=...} ...} } _object *
num_threads 0 long
pythread_stacksize 0 unsigned int
+ codec_search_path 0x00c4a260 {ob_refcnt=1 ob_type=0x7a0b8210 {python39_d.dll!_typeobject PyList_Type} {ob_base={ob_base=...} ...} } _object *
+ codec_search_cache 0x00c1f0d8 {ob_refcnt=1 ob_type=0x7a0b1178 {python39_d.dll!_typeobject PyDict_Type} {ob_base={ob_base=...} ...} } _object *
+ codec_error_registry 0x00c14f10 {ob_refcnt=1 ob_type=0x7a0b1178 {python39_d.dll!_typeobject PyDict_Type} {ob_base={ob_base=...} ...} } _object *
codecs_initialized 1 int
+ fs_codec {encoding=0x00e5aa40 "utf-8" utf8=1 errors=0x00e89ea8 "surrogatepass" ...} <unnamed-tag>
+ config {_config_init=2 isolated=0 use_environment=1 ...} PyConfig
+ dict 0x00000000 <NULL> _object *
+ builtins_copy 0x00c00a08 {ob_refcnt=1 ob_type=0x7a0b1178 {python39_d.dll!_typeobject PyDict_Type} {ob_base={ob_base=...} ...} } _object *
+ import_func 0x00bfd900 {ob_refcnt=4 ob_type=0x7a0b90d0 {python39_d.dll!_typeobject PyCFunction_Type} {ob_base={ob_base=...} ...} } _object *
eval_frame 0x79a52577 {python39_d.dll!__PyEval_EvalFrameDefault} _object *(*)(_ts *, _frame *, int)
co_extra_user_count 0 int
+ co_extra_freefuncs 0x00e5f308 {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, ...} void(*)(void *)[255]
pyexitfunc 0x79af7320 {python39_d.dll!atexit_callfuncs(_object *)} void(*)(_object *)
+ pyexitmodule 0x00f5d690 {ob_refcnt=16 ob_type=0x7a0b92d0 {python39_d.dll!_typeobject PyModule_Type} {ob_base={ob_base=...} ...} } _object *
tstate_next_unique_id 1 unsigned __int64
+ warnings {filters=0x0078edc8 {ob_refcnt=3 ob_type=0x7a0b8210 {python39_d.dll!_typeobject PyList_Type} {ob_base=...} } ...} _warnings_runtime_state
+ audit_hooks 0x00000000 <NULL> _object *
+ parser {listnode={level=0 atbol=0 } } <unnamed-tag>
+ small_ints 0x00e5f734 {0x00755868 {ob_base={ob_base={ob_refcnt=2 ob_type=0x7a0b8738 {python39_d.dll!_typeobject PyLong_Type} {...} } ...} ...}, ...} _longobject *[262]
+ autoTSSkey {_is_initialized=1 _key=5 } _Py_tss_t This looks to me like there's some kind of race condition in the thread local storage. Later, when TlsGetValue is called in PyThread_tss_get with the value of 5, the error code is lost, so I don't even know the exact reported error, and apparently 0 (nullptr) is a valid return anyways *shrug*. If I'm correctly decoding the address of the TLS slot in the TEB (I think *(unsigned int*)(void*)((@fs+0xe10)+ (20)) is the correct address for the 5th item? 4 bytes, key=5?), then there is actually a null tstate there. Not sure why. This is in a relatively recent (<1wk old) branch of the code, with some non-behavioral tweaks to add annotations in while I'm bug hunting, so it shouldn't matter. I'm not sure if I can reproduce this, but it happened, so there's a bug somewhere. What else? The signal number that tripped this was 2, sig interrupt, which makes sense. There are other threads active, so maybe that's why? The TLS was never initialized for that thread? Here's the dump from the visual studio threads window, 22316 is the active thread. Not Flagged 10360 0 Main Thread Main Thread python39_d.dll!_PyOS_WindowsConsoleReadline Not Flagged 14944 0 Worker Thread ntdll.dll!_TppWorkerThread@4�() ntdll.dll!_NtWaitForWorkViaWorkerFactory@20 Not Flagged > 22316 0 Worker Thread KernelBase.dll!_CtrlRoutine@4�() ucrtbased.dll!issue_debug_notification Not Flagged 10180 0 Worker Thread ntdll.dll!_TppWorkerThread@4�() ntdll.dll!_NtWaitForWorkViaWorkerFactory@20 Not Flagged 28940 0 Worker Thread ntdll.dll!_TppWorkerThread@4�() ntdll.dll!_NtWaitForWorkViaWorkerFactory@20 Not Flagged 9396 0 Worker Thread ntdll.dll!_TppWorkerThread@4�() ntdll.dll!_NtWaitForWorkViaWorkerFactory@20 Not Flagged 28960 0 Worker Thread ntdll.dll!_TppWorkerThread@4�() ntdll.dll!_NtWaitForWorkViaWorkerFactory@20 Anyways, I hope that's a useful report for an obscure bug. |
Lmao the name mangling comes up as a mailto. That's interesting. |
Hmmm, happens every time I interrupt while attached. Is there some obvious gotcha in the docs that I'm missing? |
On Windows, PyGILState_GetThisThreadState() returns NULL when ^C-interrupt occurs. It is from TlsGetValue() winAPI and I don't think the os's behevior is wrong. |
Oh oh. This issue is quite annoying for my work on subinterpreters. I introduced this bug when I moved pending calls from _PyRuntimeState to PyInterpreterState in bpo-39984. _PyEval_AddPendingCall() now requires tstate to add a function to pending calls of the proper interpreter. The problem on Windows is that each CTRL+c is executed in a different thread. Here is a modified Python 3.8 which dumps the thread identifier ("tid") at startup and when trip_signal() is triggered by CTRL+C: vstinner@WIN C:\vstinner\python\3.8>python
Running Release|x64 interpreter...
pymain_main: tid=1788
Python 3.8.1+ (heads/3.8-dirty:19be85c765, Apr 8 2020, 19:35:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> ^C
trip_signal: tid=6996 tstate=0000000000000000
KeyboardInterrupt
>>> ^C
trip_signal: tid=2384 tstate=0000000000000000
KeyboardInterrupt
>>> ^C
trip_signal: tid=32 tstate=0000000000000000
KeyboardInterrupt When trip_signal() is called, PyGILState_GetThisThreadState() returns NULL. |
Thanks for the bug report Alexander Riccio. I fixed bug in master. Python 3.8 is not affected. |
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: