Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions Lib/socketserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,7 @@ class will essentially render the service "deaf" while one request is
import selectors
import os
import sys
try:
import threading
except ImportError:
import dummy_threading as threading
import threading
from io import BufferedIOBase
from time import monotonic as time

Expand All @@ -144,6 +141,8 @@ class will essentially render the service "deaf" while one request is
__all__.extend(["UnixStreamServer","UnixDatagramServer",
"ThreadingUnixStreamServer",
"ThreadingUnixDatagramServer"])
if hasattr(os, "fork"):
__all__.extend(["ForkingUnixStreamServer", "ForkingUnixDatagramServer"])

# poll/select have the advantage of not requiring any extra file descriptor,
# contrarily to epoll/kqueue (also, they require a single syscall).
Expand Down Expand Up @@ -190,6 +189,7 @@ class BaseServer:
- address_family
- socket_type
- allow_reuse_address
- allow_reuse_port

Instance variables:

Expand Down Expand Up @@ -294,8 +294,7 @@ def handle_request(self):
selector.register(self, selectors.EVENT_READ)

while True:
ready = selector.select(timeout)
if ready:
if selector.select(timeout):
return self._handle_request_noblock()
else:
if timeout is not None:
Expand Down Expand Up @@ -428,6 +427,7 @@ class TCPServer(BaseServer):
- socket_type
- request_queue_size (only for stream sockets)
- allow_reuse_address
- allow_reuse_port

Instance variables:

Expand All @@ -445,6 +445,8 @@ class TCPServer(BaseServer):

allow_reuse_address = False

allow_reuse_port = False

def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
"""Constructor. May be extended, do not override."""
BaseServer.__init__(self, server_address, RequestHandlerClass)
Expand All @@ -464,8 +466,15 @@ def server_bind(self):
May be overridden.

"""
if self.allow_reuse_address:
if self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR"):
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Since Linux 6.12.9, SO_REUSEPORT is not allowed
# on other address families than AF_INET/AF_INET6.
if (
self.allow_reuse_port and hasattr(socket, "SO_REUSEPORT")
and self.address_family in (socket.AF_INET, socket.AF_INET6)
):
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()

Expand Down Expand Up @@ -522,6 +531,8 @@ class UDPServer(TCPServer):

allow_reuse_address = False

allow_reuse_port = False

socket_type = socket.SOCK_DGRAM

max_packet_size = 8192
Expand Down Expand Up @@ -723,6 +734,11 @@ class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass

class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass

if hasattr(os, "fork"):
class ForkingUnixStreamServer(ForkingMixIn, UnixStreamServer): pass

class ForkingUnixDatagramServer(ForkingMixIn, UnixDatagramServer): pass

class BaseRequestHandler:

"""Base class for request handler classes.
Expand Down
43 changes: 23 additions & 20 deletions Lib/test/test_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
import time
import unittest
from test import support
from test.support import os_helper
from test.support import (
is_apple, is_apple_mobile, os_helper, threading_helper
)
from test.support.script_helper import assert_python_ok, spawn_python
from test.support import threading_helper
try:
import _testcapi
except ImportError:
Expand Down Expand Up @@ -122,6 +123,8 @@ def __repr__(self):
self.assertEqual(signal.getsignal(signal.SIGHUP), hup)
self.assertEqual(0, argument.repr_count)

@unittest.skipIf(sys.platform.startswith("netbsd"),
"gh-124083: strsignal is not supported on NetBSD")
def test_strsignal(self):
self.assertIn("Interrupt", signal.strsignal(signal.SIGINT))
self.assertIn("Terminated", signal.strsignal(signal.SIGTERM))
Expand Down Expand Up @@ -189,8 +192,7 @@ def test_valid_signals(self):
self.assertNotIn(signal.NSIG, s)
self.assertLess(len(s), signal.NSIG)

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_issue9324(self):
# Updated for issue #10003, adding SIGBREAK
handler = lambda x, y: None
Expand Down Expand Up @@ -699,7 +701,7 @@ def handler(signum, frame):
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
class SiginterruptTest(unittest.TestCase):

def readpipe_interrupted(self, interrupt):
def readpipe_interrupted(self, interrupt, timeout=support.SHORT_TIMEOUT):
"""Perform a read during which a signal will arrive. Return True if the
read is interrupted by the signal and raises an exception. Return False
if it returns normally.
Expand Down Expand Up @@ -747,7 +749,7 @@ def handler(signum, frame):
# wait until the child process is loaded and has started
first_line = process.stdout.readline()

stdout, stderr = process.communicate(timeout=support.SHORT_TIMEOUT)
stdout, stderr = process.communicate(timeout=timeout)
except subprocess.TimeoutExpired:
process.kill()
return False
Expand Down Expand Up @@ -778,7 +780,7 @@ def test_siginterrupt_off(self):
# If a signal handler is installed and siginterrupt is called with
# a false value for the second argument, when that signal arrives, it
# does not interrupt a syscall that's in progress.
interrupted = self.readpipe_interrupted(False)
interrupted = self.readpipe_interrupted(False, timeout=2)
self.assertFalse(interrupted)


Expand Down Expand Up @@ -834,16 +836,16 @@ def test_itimer_real(self):
self.assertEqual(self.hndl_called, True)

# Issue 3864, unknown if this affects earlier versions of freebsd also
@unittest.skipIf(sys.platform in ('netbsd5',),
@unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile,
'itimer not reliable (does not mix well with threading) on some BSDs.')
def test_itimer_virtual(self):
self.itimer = signal.ITIMER_VIRTUAL
signal.signal(signal.SIGVTALRM, self.sig_vtalrm)
signal.setitimer(self.itimer, 0.3, 0.2)
signal.setitimer(self.itimer, 0.001, 0.001)

for _ in support.busy_retry(support.LONG_TIMEOUT):
# use up some virtual time by doing real work
_ = pow(12345, 67890, 10000019)
_ = sum(i * i for i in range(10**5))
if signal.getitimer(self.itimer) == (0.0, 0.0):
# sig_vtalrm handler stopped this itimer
break
Expand All @@ -860,7 +862,7 @@ def test_itimer_prof(self):

for _ in support.busy_retry(support.LONG_TIMEOUT):
# do some work
_ = pow(12345, 67890, 10000019)
_ = sum(i * i for i in range(10**5))
if signal.getitimer(self.itimer) == (0.0, 0.0):
# sig_prof handler stopped this itimer
break
Expand Down Expand Up @@ -1326,15 +1328,18 @@ def test_stress_delivery_simultaneous(self):
def handler(signum, frame):
sigs.append(signum)

self.setsig(signal.SIGUSR1, handler)
# On Android, SIGUSR1 is unreliable when used in close proximity to
# another signal – see Android/testbed/app/src/main/python/main.py.
# So we use a different signal.
self.setsig(signal.SIGUSR2, handler)
self.setsig(signal.SIGALRM, handler) # for ITIMER_REAL

expected_sigs = 0
while expected_sigs < N:
# Hopefully the SIGALRM will be received somewhere during
# initial processing of SIGUSR1.
# initial processing of SIGUSR2.
signal.setitimer(signal.ITIMER_REAL, 1e-6 + random.random() * 1e-5)
os.kill(os.getpid(), signal.SIGUSR1)
os.kill(os.getpid(), signal.SIGUSR2)

expected_sigs += 2
# Wait for handlers to run to avoid signal coalescing
Expand All @@ -1346,8 +1351,8 @@ def handler(signum, frame):
# Python handler
self.assertEqual(len(sigs), N, "Some signals were lost")

@unittest.skip("TODO: RUSTPYTHON; hang")
@unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)")
@unittest.skip('TODO: RUSTPYTHON; hang')
@unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)")
@unittest.skipUnless(hasattr(signal, "SIGUSR1"),
"test needs SIGUSR1")
@threading_helper.requires_working_threading()
Expand Down Expand Up @@ -1414,8 +1419,7 @@ def test_sigint(self):
with self.assertRaises(KeyboardInterrupt):
signal.raise_signal(signal.SIGINT)

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(sys.platform != "win32", "Windows specific test")
def test_invalid_argument(self):
try:
Expand All @@ -1439,8 +1443,7 @@ def handler(a, b):
signal.raise_signal(signal.SIGINT)
self.assertTrue(is_ok)

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test__thread_interrupt_main(self):
# See https://github.com/python/cpython/issues/102397
code = """if 1:
Expand Down
27 changes: 6 additions & 21 deletions Lib/test/test_socketserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import select
import signal
import socket
import tempfile
import threading
import unittest
import socketserver
Expand All @@ -21,21 +20,18 @@


test.support.requires("network")
test.support.requires_working_socket(module=True)


TEST_STR = b"hello world\n"
HOST = socket_helper.HOST

HAVE_UNIX_SOCKETS = hasattr(socket, "AF_UNIX")
requires_unix_sockets = unittest.skipUnless(HAVE_UNIX_SOCKETS,
'requires Unix sockets')
HAVE_FORKING = hasattr(os, "fork")
HAVE_FORKING = test.support.has_fork_support
requires_forking = unittest.skipUnless(HAVE_FORKING, 'requires forking')

def signal_alarm(n):
"""Call signal.alarm when it exists (i.e. not on Windows)."""
if hasattr(signal, 'alarm'):
signal.alarm(n)

# Remember real select() to avoid interferences with mocking
_real_select = select.select

Expand All @@ -46,14 +42,6 @@ def receive(sock, n, timeout=test.support.SHORT_TIMEOUT):
else:
raise RuntimeError("timed out on %r" % (sock,))

if HAVE_UNIX_SOCKETS and HAVE_FORKING:
class ForkingUnixStreamServer(socketserver.ForkingMixIn,
socketserver.UnixStreamServer):
pass

class ForkingUnixDatagramServer(socketserver.ForkingMixIn,
socketserver.UnixDatagramServer):
pass

@test.support.requires_fork() # TODO: RUSTPYTHON, os.fork is currently only supported on Unix-based systems
@contextlib.contextmanager
Expand All @@ -75,12 +63,10 @@ class SocketServerTest(unittest.TestCase):
"""Test all socket servers."""

def setUp(self):
signal_alarm(60) # Kill deadlocks after 60 seconds.
self.port_seed = 0
self.test_files = []

def tearDown(self):
signal_alarm(0) # Didn't deadlock.
reap_children()

for fn in self.test_files:
Expand All @@ -96,8 +82,7 @@ def pickaddr(self, proto):
else:
# XXX: We need a way to tell AF_UNIX to pick its own name
# like AF_INET provides port==0.
dir = None
fn = tempfile.mktemp(prefix='unix_socket.', dir=dir)
fn = socket_helper.create_unix_domain_name()
self.test_files.append(fn)
return fn

Expand Down Expand Up @@ -211,7 +196,7 @@ def test_ThreadingUnixStreamServer(self):
@requires_forking
def test_ForkingUnixStreamServer(self):
with simple_subprocess(self):
self.run_server(ForkingUnixStreamServer,
self.run_server(socketserver.ForkingUnixStreamServer,
socketserver.StreamRequestHandler,
self.stream_examine)

Expand Down Expand Up @@ -247,7 +232,7 @@ def test_ThreadingUnixDatagramServer(self):
@requires_unix_sockets
@requires_forking
def test_ForkingUnixDatagramServer(self):
self.run_server(ForkingUnixDatagramServer,
self.run_server(socketserver.ForkingUnixDatagramServer,
socketserver.DatagramRequestHandler,
self.dgram_examine)

Expand Down
Loading