-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
bpo-30050: Allow disabling full buffer warnings in signal.set_wakeup_fd #4792
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
Changes from all commits
4e6297b
f7cdf02
d12750c
3a9ebe3
73a5391
1eca2f3
3c2cd18
f8ac672
99b34ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| New argument warn_on_full_buffer to signal.set_wakeup_fd lets you control | ||
| whether Python prints a warning on stderr when the wakeup fd buffer | ||
| overflows. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,14 +100,15 @@ static volatile struct { | |
|
|
||
| static volatile struct { | ||
| SOCKET_T fd; | ||
| int warn_on_full_buffer; | ||
| int use_send; | ||
| int send_err_set; | ||
| int send_errno; | ||
| int send_win_error; | ||
| } wakeup = {INVALID_FD, 0, 0}; | ||
| } wakeup = {.fd = INVALID_FD, .warn_on_full_buffer = 1, .use_send = 0}; | ||
| #else | ||
| #define INVALID_FD (-1) | ||
| static volatile sig_atomic_t wakeup_fd = -1; | ||
| static volatile struct { | ||
| sig_atomic_t fd; | ||
| int warn_on_full_buffer; | ||
| } wakeup = {.fd = INVALID_FD, .warn_on_full_buffer = 1}; | ||
| #endif | ||
|
|
||
| /* Speed up sigcheck() when none tripped */ | ||
|
|
@@ -210,28 +211,15 @@ report_wakeup_write_error(void *data) | |
|
|
||
| #ifdef MS_WINDOWS | ||
| static int | ||
| report_wakeup_send_error(void* Py_UNUSED(data)) | ||
| report_wakeup_send_error(void* data) | ||
| { | ||
| PyObject *res; | ||
|
|
||
| if (wakeup.send_win_error) { | ||
| /* PyErr_SetExcFromWindowsErr() invokes FormatMessage() which | ||
| recognizes the error codes used by both GetLastError() and | ||
| WSAGetLastError */ | ||
| res = PyErr_SetExcFromWindowsErr(PyExc_OSError, wakeup.send_win_error); | ||
| } | ||
| else { | ||
| errno = wakeup.send_errno; | ||
| res = PyErr_SetFromErrno(PyExc_OSError); | ||
| } | ||
|
|
||
| assert(res == NULL); | ||
| wakeup.send_err_set = 0; | ||
|
|
||
| /* PyErr_SetExcFromWindowsErr() invokes FormatMessage() which | ||
| recognizes the error codes used by both GetLastError() and | ||
| WSAGetLastError */ | ||
| PyErr_SetExcFromWindowsErr(PyExc_OSError, (int) (intptr_t) data); | ||
| PySys_WriteStderr("Exception ignored when trying to send to the " | ||
| "signal wakeup fd:\n"); | ||
| PyErr_WriteUnraisable(NULL); | ||
|
|
||
| return 0; | ||
| } | ||
| #endif /* MS_WINDOWS */ | ||
|
|
@@ -257,7 +245,7 @@ trip_signal(int sig_num) | |
| and then set the flag, but this allowed the following sequence of events | ||
| (especially on windows, where trip_signal may run in a new thread): | ||
|
|
||
| - main thread blocks on select([wakeup_fd], ...) | ||
| - main thread blocks on select([wakeup.fd], ...) | ||
| - signal arrives | ||
| - trip_signal writes to the wakeup fd | ||
| - the main thread wakes up | ||
|
|
@@ -274,41 +262,43 @@ trip_signal(int sig_num) | |
| #ifdef MS_WINDOWS | ||
| fd = Py_SAFE_DOWNCAST(wakeup.fd, SOCKET_T, int); | ||
| #else | ||
| fd = wakeup_fd; | ||
| fd = wakeup.fd; | ||
| #endif | ||
|
|
||
| if (fd != INVALID_FD) { | ||
| byte = (unsigned char)sig_num; | ||
| #ifdef MS_WINDOWS | ||
| if (wakeup.use_send) { | ||
| do { | ||
| rc = send(fd, &byte, 1, 0); | ||
| } while (rc < 0 && errno == EINTR); | ||
|
|
||
| /* we only have a storage for one error in the wakeup structure */ | ||
| if (rc < 0 && !wakeup.send_err_set) { | ||
| wakeup.send_err_set = 1; | ||
| wakeup.send_errno = errno; | ||
| wakeup.send_win_error = GetLastError(); | ||
| /* Py_AddPendingCall() isn't signal-safe, but we | ||
| still use it for this exceptional case. */ | ||
| Py_AddPendingCall(report_wakeup_send_error, NULL); | ||
| rc = send(fd, &byte, 1, 0); | ||
|
|
||
| if (rc < 0) { | ||
| int last_error = GetLastError(); | ||
| if (wakeup.warn_on_full_buffer || | ||
| last_error != WSAEWOULDBLOCK) | ||
| { | ||
| /* Py_AddPendingCall() isn't signal-safe, but we | ||
| still use it for this exceptional case. */ | ||
| Py_AddPendingCall(report_wakeup_send_error, | ||
| (void *)(intptr_t) last_error); | ||
| } | ||
| } | ||
| } | ||
| else | ||
| #endif | ||
| { | ||
| byte = (unsigned char)sig_num; | ||
|
|
||
| /* _Py_write_noraise() retries write() if write() is interrupted by | ||
| a signal (fails with EINTR). */ | ||
| rc = _Py_write_noraise(fd, &byte, 1); | ||
|
|
||
| if (rc < 0) { | ||
| /* Py_AddPendingCall() isn't signal-safe, but we | ||
| still use it for this exceptional case. */ | ||
| Py_AddPendingCall(report_wakeup_write_error, | ||
| (void *)(intptr_t)errno); | ||
| if (wakeup.warn_on_full_buffer || | ||
| (errno != EWOULDBLOCK && errno != EAGAIN)) | ||
| { | ||
| /* Py_AddPendingCall() isn't signal-safe, but we | ||
| still use it for this exceptional case. */ | ||
| Py_AddPendingCall(report_wakeup_write_error, | ||
| (void *)(intptr_t)errno); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -549,9 +539,13 @@ signal_siginterrupt_impl(PyObject *module, int signalnum, int flag) | |
|
|
||
|
|
||
| static PyObject* | ||
| signal_set_wakeup_fd(PyObject *self, PyObject *args) | ||
| signal_set_wakeup_fd(PyObject *self, PyObject *args, PyObject *kwds) | ||
| { | ||
| struct _Py_stat_struct status; | ||
| static char *kwlist[] = { | ||
| "", "warn_on_full_buffer", NULL, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| }; | ||
| int warn_on_full_buffer = 1; | ||
| #ifdef MS_WINDOWS | ||
| PyObject *fdobj; | ||
| SOCKET_T sockfd, old_sockfd; | ||
|
|
@@ -560,7 +554,8 @@ signal_set_wakeup_fd(PyObject *self, PyObject *args) | |
| PyObject *mod; | ||
| int is_socket; | ||
|
|
||
| if (!PyArg_ParseTuple(args, "O:set_wakeup_fd", &fdobj)) | ||
| if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|$p:set_wakeup_fd", kwlist, | ||
| &fdobj, &warn_on_full_buffer)) | ||
| return NULL; | ||
|
|
||
| sockfd = PyLong_AsSocket_t(fdobj); | ||
|
|
@@ -569,7 +564,8 @@ signal_set_wakeup_fd(PyObject *self, PyObject *args) | |
| #else | ||
| int fd, old_fd; | ||
|
|
||
| if (!PyArg_ParseTuple(args, "i:set_wakeup_fd", &fd)) | ||
| if (!PyArg_ParseTupleAndKeywords(args, kwds, "i|$p:set_wakeup_fd", kwlist, | ||
| &fd, &warn_on_full_buffer)) | ||
| return NULL; | ||
| #endif | ||
|
|
||
|
|
@@ -620,6 +616,7 @@ signal_set_wakeup_fd(PyObject *self, PyObject *args) | |
|
|
||
| old_sockfd = wakeup.fd; | ||
| wakeup.fd = sockfd; | ||
| wakeup.warn_on_full_buffer = warn_on_full_buffer; | ||
| wakeup.use_send = is_socket; | ||
|
|
||
| if (old_sockfd != INVALID_FD) | ||
|
|
@@ -644,15 +641,16 @@ signal_set_wakeup_fd(PyObject *self, PyObject *args) | |
| } | ||
| } | ||
|
|
||
| old_fd = wakeup_fd; | ||
| wakeup_fd = fd; | ||
| old_fd = wakeup.fd; | ||
| wakeup.fd = fd; | ||
| wakeup.warn_on_full_buffer = warn_on_full_buffer; | ||
|
|
||
| return PyLong_FromLong(old_fd); | ||
| #endif | ||
| } | ||
|
|
||
| PyDoc_STRVAR(set_wakeup_fd_doc, | ||
| "set_wakeup_fd(fd) -> fd\n\ | ||
| "set_wakeup_fd(fd, *, warn_on_full_buffer=True) -> fd\n\ | ||
| \n\ | ||
| Sets the fd to be written to (with the signal number) when a signal\n\ | ||
| comes in. A library can use this to wakeup select or poll.\n\ | ||
|
|
@@ -670,11 +668,11 @@ PySignal_SetWakeupFd(int fd) | |
|
|
||
| #ifdef MS_WINDOWS | ||
| old_fd = Py_SAFE_DOWNCAST(wakeup.fd, SOCKET_T, int); | ||
| wakeup.fd = fd; | ||
| #else | ||
| old_fd = wakeup_fd; | ||
| wakeup_fd = fd; | ||
| old_fd = wakeup.fd; | ||
| #endif | ||
| wakeup.fd = fd; | ||
| wakeup.warn_on_full_buffer = 1; | ||
| return old_fd; | ||
| } | ||
|
|
||
|
|
@@ -1155,7 +1153,7 @@ static PyMethodDef signal_methods[] = { | |
| SIGNAL_GETITIMER_METHODDEF | ||
| SIGNAL_SIGNAL_METHODDEF | ||
| SIGNAL_GETSIGNAL_METHODDEF | ||
| {"set_wakeup_fd", signal_set_wakeup_fd, METH_VARARGS, set_wakeup_fd_doc}, | ||
| {"set_wakeup_fd", (PyCFunction)signal_set_wakeup_fd, METH_VARARGS | METH_KEYWORDS, set_wakeup_fd_doc}, | ||
| SIGNAL_SIGINTERRUPT_METHODDEF | ||
| SIGNAL_PAUSE_METHODDEF | ||
| SIGNAL_PTHREAD_KILL_METHODDEF | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_buffervalues leaked between different calls toset_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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.