Issue #7978: socketserver now restarts the select() call when EINTR is returned.
This avoids crashing the server loop when a signal is received. Patch by Jerzy Kozera.
This commit is contained in:
parent
088a874c7f
commit
fa1d84107a
|
@ -133,6 +133,7 @@ import socket
|
||||||
import select
|
import select
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import errno
|
||||||
try:
|
try:
|
||||||
import threading
|
import threading
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -147,6 +148,15 @@ if hasattr(socket, "AF_UNIX"):
|
||||||
"ThreadingUnixStreamServer",
|
"ThreadingUnixStreamServer",
|
||||||
"ThreadingUnixDatagramServer"])
|
"ThreadingUnixDatagramServer"])
|
||||||
|
|
||||||
|
def _eintr_retry(func, *args):
|
||||||
|
"""restart a system call interrupted by EINTR"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
return func(*args)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EINTR:
|
||||||
|
raise
|
||||||
|
|
||||||
class BaseServer:
|
class BaseServer:
|
||||||
|
|
||||||
"""Base class for server classes.
|
"""Base class for server classes.
|
||||||
|
@ -222,7 +232,8 @@ class BaseServer:
|
||||||
# connecting to the socket to wake this up instead of
|
# connecting to the socket to wake this up instead of
|
||||||
# polling. Polling reduces our responsiveness to a
|
# polling. Polling reduces our responsiveness to a
|
||||||
# shutdown request and wastes cpu at all other times.
|
# shutdown request and wastes cpu at all other times.
|
||||||
r, w, e = select.select([self], [], [], poll_interval)
|
r, w, e = _eintr_retry(select.select, [self], [], [],
|
||||||
|
poll_interval)
|
||||||
if self in r:
|
if self in r:
|
||||||
self._handle_request_noblock()
|
self._handle_request_noblock()
|
||||||
finally:
|
finally:
|
||||||
|
@ -262,7 +273,7 @@ class BaseServer:
|
||||||
timeout = self.timeout
|
timeout = self.timeout
|
||||||
elif self.timeout is not None:
|
elif self.timeout is not None:
|
||||||
timeout = min(timeout, self.timeout)
|
timeout = min(timeout, self.timeout)
|
||||||
fd_sets = select.select([self], [], [], timeout)
|
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
|
||||||
if not fd_sets[0]:
|
if not fd_sets[0]:
|
||||||
self.handle_timeout()
|
self.handle_timeout()
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,6 +8,8 @@ import os
|
||||||
import select
|
import select
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
|
import select
|
||||||
|
import errno
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
import SocketServer
|
import SocketServer
|
||||||
|
@ -225,6 +227,38 @@ class SocketServerTest(unittest.TestCase):
|
||||||
SocketServer.DatagramRequestHandler,
|
SocketServer.DatagramRequestHandler,
|
||||||
self.dgram_examine)
|
self.dgram_examine)
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def mocked_select_module(self):
|
||||||
|
"""Mocks the select.select() call to raise EINTR for first call"""
|
||||||
|
old_select = select.select
|
||||||
|
|
||||||
|
class MockSelect:
|
||||||
|
def __init__(self):
|
||||||
|
self.called = 0
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
self.called += 1
|
||||||
|
if self.called == 1:
|
||||||
|
# raise the exception on first call
|
||||||
|
raise OSError(errno.EINTR, os.strerror(errno.EINTR))
|
||||||
|
else:
|
||||||
|
# Return real select value for consecutive calls
|
||||||
|
return old_select(*args)
|
||||||
|
|
||||||
|
select.select = MockSelect()
|
||||||
|
try:
|
||||||
|
yield select.select
|
||||||
|
finally:
|
||||||
|
select.select = old_select
|
||||||
|
|
||||||
|
def test_InterruptServerSelectCall(self):
|
||||||
|
with self.mocked_select_module() as mock_select:
|
||||||
|
pid = self.run_server(SocketServer.TCPServer,
|
||||||
|
SocketServer.StreamRequestHandler,
|
||||||
|
self.stream_examine)
|
||||||
|
# Make sure select was called again:
|
||||||
|
self.assertGreater(mock_select.called, 1)
|
||||||
|
|
||||||
# Alas, on Linux (at least) recvfrom() doesn't return a meaningful
|
# Alas, on Linux (at least) recvfrom() doesn't return a meaningful
|
||||||
# client address so this cannot work:
|
# client address so this cannot work:
|
||||||
|
|
||||||
|
|
|
@ -461,6 +461,7 @@ Greg Kochanski
|
||||||
Damon Kohler
|
Damon Kohler
|
||||||
Marko Kohtala
|
Marko Kohtala
|
||||||
Joseph Koshy
|
Joseph Koshy
|
||||||
|
Jerzy Kozera
|
||||||
Maksim Kozyarchuk
|
Maksim Kozyarchuk
|
||||||
Stefan Krah
|
Stefan Krah
|
||||||
Bob Kras
|
Bob Kras
|
||||||
|
|
|
@ -44,6 +44,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #7978: SocketServer now restarts the select() call when EINTR is
|
||||||
|
returned. This avoids crashing the server loop when a signal is received.
|
||||||
|
Patch by Jerzy Kozera.
|
||||||
|
|
||||||
- Issue #14409: IDLE now properly executes commands in the Shell window
|
- Issue #14409: IDLE now properly executes commands in the Shell window
|
||||||
when it cannot read the normal config files on startup and
|
when it cannot read the normal config files on startup and
|
||||||
has to use the built-in default key bindings.
|
has to use the built-in default key bindings.
|
||||||
|
|
Loading…
Reference in New Issue