-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
Regression caused by CALL_FUNCTION specialization for C function calls (test_urllib fails when run multiple times) #90623
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
Comments
test_unittest: TestBreakSignalDefault.testInstallHandler() fails if run after TestBreak: $ ./python -m test -v test_unittest -R 3:3 -m '*TestBreak.testHandlerReplacedButCalled' -m '*TestBreak.testInstallHandler' -m '*TestBreakSignalDefault.testInstallHandler'
(...)
beginning 6 repetitions
123456
testHandlerReplacedButCalled (unittest.test.test_break.TestBreak) ... ok
testInstallHandler (unittest.test.test_break.TestBreak) ... ok
testInstallHandler (unittest.test.test_break.TestBreakSignalDefault) ... ok Ran 3 tests in 0.003s OK ====================================================================== Traceback (most recent call last):
File "/home/vstinner/python/main/Lib/unittest/test/test_break.py", line 38, in testInstallHandler
self.assertTrue(unittest.signals._interrupt_handler.called)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: False is not true
(...) Or you can use a "bisect" file: And the command: ./python -m test -v -F test_unittest --matchfile=bisect Or the command: ./python -m test -v test_unittest -R 3:3 --matchfile=bisect It fails starting at the 4th iteration. |
It's a regression introduced by the following change: commit 3163e68
|
Attached bug.py reproduces the bug without unittest, just run: ./python bug.py On Python 3.10, it writes: On the main branch, it fails with: LOOP 0
LOOP 1
LOOP 2
LOOP 3
LOOP 4
LOOP 5
LOOP 6
LOOP 7
Traceback (most recent call last):
File "/home/vstinner/bug.py", line 24, in <module>
Bug().run()
^^^^^^^^^^^
File "/home/vstinner/bug.py", line 19, in run
raise Exception("bug")
^^^^^^^^^^^^^^^^^^^^^^
Exception: bug |
@kj, are you looking into this? |
The problem is that the optimization no longer checks for pending signals in TARGET(CALL_NO_KW_BUILTIN_FAST). The patch below fix my issue. I guess that other opcode needs an additional CHECK_EVAL_BREAKER(). diff --git a/Python/ceval.c b/Python/ceval.c
index 9aaddd99eda..7cc0f805366 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -4822,6 +4822,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
*/
goto error;
}
+ CHECK_EVAL_BREAKER();
DISPATCH();
} |
It seems like the following target miss CHECK_EVAL_BREAKER(): TARGET(CALL_NO_KW_METHOD_DESCRIPTOR_FAST)
TARGET(CALL_NO_KW_METHOD_DESCRIPTOR_O)
TARGET(CALL_NO_KW_BUILTIN_FAST)
TARGET(CALL_NO_KW_BUILTIN_O)
TARGET(CALL_NO_KW_BUILTIN_CLASS_1) CHECK_EVAL_BREAKER() matters for signals, but also multithreading (drop the GIL), asynchronous exception and pending calls. |
Thanks @victor for catching this and figuring it out. I had a hard time narrowing this down since I'm on Windows which doesn't support signals :(. I'll handle the required patch and tests. |
A short summary (thanks to Victor's findings!):
I'd like to add tests for some of the other CALL_X in the future, but I'm a little short on time at the moment, sorry! For now, I've tried to turn some of the tests "hot" to see if I can get those to trigger. |
Is this a bug? Signal handling in Python is asynchronous. https://docs.python.org/3/library/signal.html#execution-of-python-signal-handlers The example code tests whether the interpreter responds synchronously and immediately. If you add `for _in range(1): pass` or a call to any Python function in between the `os.kill(pid, SIGNUM)` and the `if not self.called:` then the test passes. |
In Python 3.10, the code works. In Python 3.11, it fails. It's a behavior change. IMO this change is unwanted. I expect that signals are handled "as soon as possible", *especially* if we receive it "during" an os.kill() call on the current process on purpose. |
It's also interesting to note that the implementation of os.kill() and signal.raise_signal() do *not* call PyErr_CheckSignal(). The following signal functions *do* call call PyErr_CheckSignal():
Some other signal functions call PyErr_CheckSignal() if a syscall fails with EINTR (PEP-475). |
See my previous comment: "CHECK_EVAL_BREAKER() matters for signals, but also multithreading (drop the GIL), asynchronous exception and pending calls." If a thread executes a function which only uses opcodes which don't call CHECK_EVAL_BREAKER(), I understand that it can eat more than its slice of 5 ms, see sys.getswitchinterval(): So it's also a threads scheduling issue, no? |
No, it isn't. The interpreter checks the eval breaker frequently enough. It checks on all back edges and on all calls to Python functions. The test probably needs to be fixed, or extended. It is signals sent from another process or thread that we should be testing for. I'm happy to merge PR 30826, but if you are really concerned about prompt delivery of signals, then you should be worried about C extensions. If you are worried about being able to interrupt programs, then you also Regarding C extensions, I think clear documentation that extension authors need to check for signals in any code that might run for a few hundred microseconds or longer is the best approach. For `except:`, maybe we could issue a syntax warning, as `except:` is universally considered to be bad practice. |
Python 3.10 doesn't require it. If you want to change Python 3.11 behavior, please announce the change on python-dev and capi-sig mailing lists, and document it carefully in What's New in Python 3.11. I'm not talking about checking frequently interruptions while a function is running, I'm only talking about the "end" of a C function (exit). In Python 3.10, ceval.c does it automatically. case TARGET(CALL_FUNCTION): {
PREDICTED(CALL_FUNCTION);
PyObject **sp, *res;
sp = stack_pointer;
res = call_function(tstate, &trace_info, &sp, oparg, NULL);
stack_pointer = sp;
PUSH(res);
if (res == NULL) {
goto error;
}
CHECK_EVAL_BREAKER(); // <==== HERE
DISPATCH();
} |
I confirm that attached bug.py script no longer fails on the current main branch. Moreover, #90623 (comment) test no longer fails: I ran |
signal.raise_signal() and os.kill() now call PyErr_CheckSignals() to check immediately for pending signals.
Merged. I close the issue. Thanks for the ceval.c fix! |
CALL
opcodes #30826CALL
opcodes #31404Note: 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: