Conversation
Embedding hosts such as Qt install a non-Python SIGINT handler, so round-tripping it through signal.signal() raises TypeError. That is not the same as running off the main thread (ValueError), and treating it as such skipped Trio's signal setup. Fixes python-trio#2564.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3524 +/- ##
===============================================
Coverage 100.00000% 100.00000%
===============================================
Files 128 128
Lines 19471 19530 +59
Branches 1323 1326 +3
===============================================
+ Hits 19471 19530 +59
🚀 New features to boost your workflow:
|
|
Thanks but I think this is the wrong solution to this. In particular, it is the main thread and we should be adding our SIGINT handler. Edit: hmm, just realized I got my logic inverted. 🤔 I'm not convinced this is right and we don't have tests for it. I guess I'd be interested in some test cases! |
|
Agreed the current approach needs a rethink — I flipped the “is this the main thread?” check the wrong way for the C-level SIGINT case. I’ll come back with tests that cover the real scenario before pushing another attempt. |
|
Added regression tests in ddfcc2c, including an embedded CPython host with a real C SIGINT handler. Main-thread detection succeeds while worker-thread detection remains false; KIManager preserves the host handler. Testing exposed a cleanup bug: open_signal_receiver(SIGINT) could replace the native handler and then fail to restore None. It now checks all requested handlers before changing any and rejects native handlers that Python cannot restore; unrelated signal receivers remain usable. The native host confirms four SIGINT deliveries reach the original handler. Targeted signal/util suites: Python 3.12, 36 passed and 1 xfailed; Python 3.11, 35 passed, 1 skipped and 1 xfailed. Black and Ruff pass. This does not claim the existing upstream PyPy/OpenSSL CI failures are fixed. |
Summary
Fixes #2564.
is_main_thread()currently treats any failure fromsignal.signal(SIGINT, getsignal(SIGINT))as "not the main thread". That is correct forValueError(the CPython error when you are not on the main thread of the main interpreter). It is wrong forTypeError.When an embedding host such as Qt/PySide installs a C-level
SIGINThandler,signal.getsignal(SIGINT)returnsNone. Round-tripping that throughsignal.signal()raises:That
TypeErrorstill means we reachedsignal.signal(), so we are on the main thread. ReturningFalsemade Trio skip signal setup (open_signal_receiver, the wakeup fd, etc.) even though it was running on the real main thread.KIManager.installis unchanged for this case: it still refuses to replace a non-defaultSIGINThandler.Test plan
test_is_main_thread_with_non_python_sigint_handlermockssignal.getsignal(SIGINT) -> Noneis_main_thread()isFalse)Trueon the main thread, stillFalsefrom a worker thread)test_is_main_threadandtest_signals.pystill pass