Skip to content

Conversation

@njsmith
Copy link
Contributor

@njsmith njsmith commented Dec 11, 2017

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

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.
@njsmith
Copy link
Contributor Author

njsmith commented Dec 11, 2017

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 signalmodule.c even more by bringing the Windows and POSIX code closer together, and I also noticed that our SOCKET_T handling is rather dubious – we spend a lot of energy on using SOCKET_T on Windows and int on POSIX, but then at key points we cast SOCKET_T to int anyway. But that will have to wait for another time.)

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

int send_errno;
int send_win_error;
} wakeup = {INVALID_FD, 0, 0};
} wakeup = {INVALID_FD, 1, 0};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to use C99 initializers:

wakeup = {.fd = INVALID_FD, .warn_on_full_buffer = 1, use_end=0};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

static volatile struct {
sig_atomic_t fd;
int warn_on_full_buffer;
} wakeup = {INVALID_FD, 1};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wakeup = {.fd = INVALID_FD, .warn_on_full_buffer = 1};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweaked the text to emphasize that this is a rare condition.

Py_AddPendingCall(report_wakeup_write_error,
(void *)(intptr_t)errno);
if (wakeup.warn_on_full_buffer ||
(errno != EWOULDBLOCK && errno != EAGAIN)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 7: for multiline condition, please put { on a new line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

signum = signal.SIGINT
def handler(signum, frame):
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment explaining that the signal handler is called but don't read pending signals from the socket.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

# Fill the send buffer
try:
while True:
write.send(b"x" * 1024)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is send(1024 bytes) fails with BlockingIOError, but send(1 bytes) works?

I suggest to loop on write.send(b"x") instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):
Copy link
Member

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.

Copy link
Contributor Author

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_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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

{
struct _Py_stat_struct status;
static char *kwlist[] = {
"", "warn_on_full_buffer", NULL,
Copy link
Member

Choose a reason for hiding this comment

The 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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@vstinner
Copy link
Member

@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.

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just a few remaining nitpicking.

But as I wrote, I would prefer to get review of at least a second core developer.

On Windows, the function now also supports socket handles.

.. versionchanged:: 3.7
Added ``warn_on_full_buffer``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added warn_on_full_buffer parameter.


def test_invalid_call(self):
with self.assertRaises(TypeError):
signal.set_wakeup_fd(signum=signal.SIGINT)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

@vstinner
Copy link
Member

Ah, there are some issues on Windows. AppVeyor failed to compile:

..\Modules\signalmodule.c(219): error C2065: 'res': undeclared identifier [C:\projects\cpython\PCbuild\pythoncore.vcxproj] ..\Modules\signalmodule.c(219): warning C4047: '=': 'int' differs in levels of indirection from 'PyObject *' [C:\projects\cpython\PCbuild\pythoncore.vcxproj] 
@njsmith
Copy link
Contributor Author

njsmith commented Dec 12, 2017

Huh, weird that appveyor didn't highlight that the first time! Anyway, fixed the appeyor build (I hope) and addressed all comments.

@vstinner
Copy link
Member

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 :-)

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

5 participants