commit 99f4e2d92c882b96f81a01975e110dcc9c4fb29a
parent 5b1d19df06028513afd9f398ae3d0bab7eeb8456
Author: Sam Sneddon <gsnedders@apple.com>
Date: Fri, 31 Oct 2025 08:41:56 +0000
Bug 1974140 [wpt PR 53407] - Override serve_forever to use a socket pair and select, a=testonly
Automatic update from web-platform-tests
Override serve_forever to use a socket pair and select
This overrides the superclass implementation to use a socket pair to
process shutdown requests, avoiding having to wait the poll_interval
before shutting down.
While we only save up to half a second for a single `wpt run`
invocation, we much more dramatically save time with our server unit
tests, where we create a new start and shutdown a new server for each
test.
Our choice of a socket pair is motivated by Windows support: you
cannot select() a pipe on Windows, but you can select a socket.
--
wpt-commits: 957e3f0adc41284b9b734da926b80281a3a21c3a
wpt-pr: 53407
Diffstat:
1 file changed, 49 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/tools/wptserve/wptserve/server.py b/testing/web-platform/tests/tools/wptserve/wptserve/server.py
@@ -6,6 +6,7 @@ import http.server
import ipaddress
import os
import platform
+import selectors
import socket
import socketserver
import ssl
@@ -182,6 +183,9 @@ class WebTestServer(http.server.ThreadingHTTPServer):
:param latency: Delay in ms to wait before serving each response, or
callable that returns a delay in ms
"""
+ self._shutdown_event = threading.Event()
+ self._shutdown_write_sock = None
+
self.router = router
self.rewriter = rewriter
@@ -249,6 +253,51 @@ class WebTestServer(http.server.ThreadingHTTPServer):
self.server_name = socket.getfqdn(host)
self.server_port = port
+ def serve_forever(self, poll_interval=0.5):
+ """Handle one request at a time until shutdown.
+
+ This overrides the superclass implementation to use a socket pair to process
+ shutdown requests, avoiding waiting the poll_interval before shutting down.
+ It does, however, still call service_actions() every poll_interval.
+
+ """
+ shutdown_read_sock, self._shutdown_write_sock = socket.socketpair()
+ self._shutdown_event.clear()
+
+ try:
+ with selectors.DefaultSelector() as selector:
+ selector.register(self, selectors.EVENT_READ)
+ selector.register(shutdown_read_sock, selectors.EVENT_READ)
+
+ while True:
+ events = selector.select(timeout=poll_interval)
+
+ # Handle shutdown requests before any request
+ if any(
+ key.fileobj == shutdown_read_sock and mask == selectors.EVENT_READ
+ for key, mask in events
+ ):
+ shutdown_read_sock.recv(1)
+ break
+
+ for key, mask in events:
+ if key.fileobj == self and mask == selectors.EVENT_READ:
+ super()._handle_request_noblock()
+ else:
+ assert False, "unreachable"
+ else:
+ self.service_actions()
+
+ finally:
+ shutdown_read_sock.close()
+ self._shutdown_write_sock.close()
+ self._shutdown_event.set()
+
+ def shutdown(self):
+ """Stops the serve_forever loop and waits for it to finish."""
+ self._shutdown_write_sock.send(b'x')
+ self._shutdown_event.wait()
+
def finish_request(self, request, client_address):
if isinstance(self.socket, ssl.SSLSocket):
request.do_handshake()