mirror of
https://github.com/python/cpython.git
synced 2025-10-09 16:34:44 +00:00
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:
commit
c9e8e3c4dd
4 changed files with 52 additions and 2 deletions
|
@ -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.
|
||||||
|
@ -223,7 +233,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()
|
||||||
|
|
||||||
|
@ -273,7 +284,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
|
||||||
|
@ -226,6 +228,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:
|
||||||
|
|
||||||
|
|
|
@ -559,6 +559,7 @@ Jacek Konieczny
|
||||||
Arkady Koplyarov
|
Arkady Koplyarov
|
||||||
Vlad Korolev
|
Vlad Korolev
|
||||||
Joseph Koshy
|
Joseph Koshy
|
||||||
|
Jerzy Kozera
|
||||||
Maksim Kozyarchuk
|
Maksim Kozyarchuk
|
||||||
Stefan Krah
|
Stefan Krah
|
||||||
Bob Kras
|
Bob Kras
|
||||||
|
|
|
@ -19,6 +19,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 #14522: Avoid duplicating socket handles in multiprocessing.connection.
|
- Issue #14522: Avoid duplicating socket handles in multiprocessing.connection.
|
||||||
Patch by sbt.
|
Patch by sbt.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue