bpo-30050: Allow disabling full buffer warnings in signal.set_wakeup_fd#4792
bpo-30050: Allow disabling full buffer warnings in signal.set_wakeup_fd#47921st1 merged 9 commits intopython:masterfrom
Conversation
No semantic changes.
This variable is already set to this value directly above. No semantic change.
This commit has no behavioral changes.
When a send() on Windows failed in signal handler context, we used to
do a lot of hoop-jumping to pass both errno and the GetLastError()
back out so they could be reported. But... send() on Windows does not
set errno:
"[If an error occurs], a value of SOCKET_ERROR is returned, and a
specific error code can be retrieved by calling WSAGetLastError."
-- https://msdn.microsoft.com/en-us/library/windows/desktop/ms740149(v=vs.85).aspx
This allows the code to be substantially simplified.
Also, there is no concept of EINTR on Windows, and even if there was
then it wouldn't be reported through errno, so there's no need to loop
checking for errno == EINTR.
|
This PR will be easiest to review one commit at a time: each commit is self-contained, and the first few are brush-clearing refactorings. (I think there's potential to simplify |
vstinner
left a comment
There was a problem hiding this comment.
I like the overall change, but I have a few comments.
Would you mind to document the new optional parameter in the signal section of Doc/whatsnew/3.7.rst please?
Modules/signalmodule.c
Outdated
| int send_errno; | ||
| int send_win_error; | ||
| } wakeup = {INVALID_FD, 0, 0}; | ||
| } wakeup = {INVALID_FD, 1, 0}; |
There was a problem hiding this comment.
I suggest to use C99 initializers:
wakeup = {.fd = INVALID_FD, .warn_on_full_buffer = 1, use_end=0};
Modules/signalmodule.c
Outdated
| static volatile struct { | ||
| sig_atomic_t fd; | ||
| int warn_on_full_buffer; | ||
| } wakeup = {INVALID_FD, 1}; |
There was a problem hiding this comment.
wakeup = {.fd = INVALID_FD, .warn_on_full_buffer = 1};
Doc/library/signal.rst
Outdated
| the byte values give you the signal numbers. This is simple, but it | ||
| can run into a problem: generally the fd will have a limited amount | ||
| of buffer space, and if too many signals arrive too quickly, the | ||
| buffer may become full, and some signals may be lost. If you use |
There was a problem hiding this comment.
I propose to add something like "if many signals are received quickly, faster that what the application can read".
I never saw this warning, there is need to scare users ;-)
There was a problem hiding this comment.
Tweaked the text to emphasize that this is a rare condition.
Modules/signalmodule.c
Outdated
| Py_AddPendingCall(report_wakeup_write_error, | ||
| (void *)(intptr_t)errno); | ||
| if (wakeup.warn_on_full_buffer || | ||
| (errno != EWOULDBLOCK && errno != EAGAIN)) { |
There was a problem hiding this comment.
PEP 7: for multiline condition, please put { on a new line.
| signum = signal.SIGINT | ||
|
|
||
| def handler(signum, frame): | ||
| pass |
There was a problem hiding this comment.
Please add a comment explaining that the signal handler is called but don't read pending signals from the socket.
Lib/test/test_signal.py
Outdated
| # Fill the send buffer | ||
| try: | ||
| while True: | ||
| write.send(b"x" * 1024) |
There was a problem hiding this comment.
What is send(1024 bytes) fails with BlockingIOError, but send(1 bytes) works?
I suggest to loop on write.send(b"x") instead.
There was a problem hiding this comment.
I don't think this can happen in any real system (you'd get a partial send instead), but I checked and sending 1 byte at a time is only ~2x slower, so why not. Done.
| assert_python_ok('-c', code) | ||
|
|
||
| @unittest.skipIf(_testcapi is None, 'need _testcapi') | ||
| def test_warn_on_full_buffer(self): |
There was a problem hiding this comment.
Please try to split this test into 3 tests, and handle stderr in the parent process. Running 3 tests in the same process may have side effects between tests.
There was a problem hiding this comment.
I'd rather not? Part of what I want to test is that there are no side-effects between tests :-). (As there could be e.g. if warn_on_full_buffer values leaked between different calls to set_wakeup_fd.) And the handling of stderr within the child process is copied straight from the other wakeup fd warning tests in the same file.
| { | ||
| struct _Py_stat_struct status; | ||
| static char *kwlist[] = { | ||
| "", "warn_on_full_buffer", NULL, |
There was a problem hiding this comment.
Please write an unit test to make sure that it's not possible to pass the signal number as a keyword argument.
Dummy test like: with self.assertRaises(TypeError): signal.set_wakeup_fd(signum=signal.SIGINT)
|
@pitrou, @serhiy-storchaka: I would feel more confortable if this change would be double checked, since signals are complex and is a critical component. IHMO it's still ok to add the new parameter in Python 3.7. |
vstinner
left a comment
There was a problem hiding this comment.
LGTM, just a few remaining nitpicking.
But as I wrote, I would prefer to get review of at least a second core developer.
Doc/library/signal.rst
Outdated
| On Windows, the function now also supports socket handles. | ||
|
|
||
| .. versionchanged:: 3.7 | ||
| Added ``warn_on_full_buffer``. |
There was a problem hiding this comment.
Added warn_on_full_buffer parameter.
|
|
||
| def test_invalid_call(self): | ||
| with self.assertRaises(TypeError): | ||
| signal.set_wakeup_fd(signum=signal.SIGINT) |
There was a problem hiding this comment.
I suggest a short comment: "# first parameter is positional-only"
| signal.set_wakeup_fd(signum=signal.SIGINT) | ||
|
|
||
| with self.assertRaises(TypeError): | ||
| signal.set_wakeup_fd(signal.SIGINT, False) |
There was a problem hiding this comment.
I suggest a short comment: "# warn_on_full_buffer is a keyword-only parameter"
| assert_python_ok('-c', code) | ||
|
|
||
| @unittest.skipIf(_testcapi is None, 'need _testcapi') | ||
| def test_warn_on_full_buffer(self): |
|
Ah, there are some issues on Windows. AppVeyor failed to compile: |
|
Huh, weird that appveyor didn't highlight that the first time! Anyway, fixed the appeyor build (I hope) and addressed all comments. |
AppVeyor didn't run the first time. There is an heuristic to not run AppVeyor on changes which are only documentation or specific to Linux. The heuristic isn't perfect :-) |
Previously, if the wakeup fd's buffer overflowed, Python would
unconditionally print a warning on stderr. But for many of
set_wakeup_fd's users (e.g. Twisted, Tornado, and Trio), this is
incorrect: the way they use set_wakeup_fd, a full buffer is a totally
normal and unexceptional condition. On the other hand, for asyncio and
curio, a full buffer causes signals to be lost, so they really want to
issue a warning (at the least!).
This change lets the caller of set_wakeup_fd choose whether they would
like this warning to be printed or not.
https://bugs.python.org/issue30050