Skip to main content

Lib/socketserver.py

cpython 3.14 @ ab2d84fe1023/Lib/socketserver.py

socketserver is a pure-Python server framework. It factors the server lifecycle (bind, listen, accept, dispatch, shutdown) away from the concurrency policy (serial, forking, threading) and the request handling logic.

The class hierarchy has two axes. On the server axis: BaseServer holds the serve_forever loop and the handle_request dispatch; TCPServer and UDPServer add socket creation and server_bind/server_activate; UnixStreamServer and UnixDatagramServer are AF_UNIX specializations. On the handler axis: BaseRequestHandler defines the setup/handle/ finish template; StreamRequestHandler wraps the connection in makefile-backed rfile/wfile; DatagramRequestHandler supplies a BytesIO buffer pair.

The two axes are composed by mixing ForkingMixIn or ThreadingMixIn with any server class. The mixin overrides process_request so each accepted connection is dispatched to a new process or thread.

Map

LinesSymbolRolegopy
1-100Module docstring, imports, __all__Lists the public API; documents the four concrete server classes and the two mixins.(stdlib pending)
100-250BaseServerserve_forever select loop, handle_request, _handle_request_noblock, process_request, finish_request, shutdown, server_close.(stdlib pending)
250-420TCPServer__init__ creates the socket and calls server_bind/server_activate; get_request calls accept; allow_reuse_address, allow_reuse_port flags; server_close closes the socket.(stdlib pending)
420-560UDPServerOverrides get_request to use recvfrom; socket_type = SOCK_DGRAM; server_activate is a no-op (UDP has no listen).(stdlib pending)
420-560UnixStreamServer, UnixDatagramServerSet address_family = AF_UNIX; otherwise inherit TCPServer and UDPServer respectively.(stdlib pending)
560-660ForkingMixInOverrides process_request to fork(); parent registers the child PID; collect_children reaps zombies via waitpid; block_on_close joins children on server shutdown.(stdlib pending)
660-730ThreadingMixInOverrides process_request to start a Thread targeting process_request_thread; daemon_threads flag controls daemonization; block_on_close joins all active threads on shutdown.(stdlib pending)
730-800BaseRequestHandler, StreamRequestHandler, DatagramRequestHandlerBaseRequestHandler.__init__ calls setup, handle, finish in sequence; StreamRequestHandler.setup creates rfile/wfile via socket.makefile; DatagramRequestHandler.setup uses BytesIO.(stdlib pending)

Reading

serve_forever select loop (lines 100 to 250)

cpython 3.14 @ ab2d84fe1023/Lib/socketserver.py#L100-250

def serve_forever(self, poll_interval=0.5):
self.__is_shut_down.clear()
try:
with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(poll_interval)
# bpo-35017: shutdown() called during select(), exit immediately.
if self.__shutdown_request:
break
if ready:
self._handle_request_noblock()
self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()

serve_forever uses selectors.DefaultSelector (aliased as _ServerSelector) to wait for the listening socket to become readable. The poll_interval cap (default 0.5 s) ensures the __shutdown_request flag is checked regularly even if no connection arrives. shutdown() sets the flag from another thread and then blocks on __is_shut_down.wait(). service_actions() is a no-op hook that subclasses can override for periodic maintenance (e.g., reaping forked children in ForkingMixIn).

ThreadingMixIn.process_request_thread (lines 660 to 730)

cpython 3.14 @ ab2d84fe1023/Lib/socketserver.py#L660-730

class ThreadingMixIn:
daemon_threads = False
block_on_close = True
_threads = None

def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
except Exception:
self.handle_error(request, client_address)
finally:
self.shutdown_request(request)

def process_request(self, request, client_address):
t = threading.Thread(target=self.process_request_thread,
args=(request, client_address))
t.daemon = self.daemon_threads
if self.block_on_close:
if self._threads is None:
with self._threads_lock:
if self._threads is None:
self._threads = []
self._threads.append(t)
t.start()

def server_close(self):
super().server_close()
if self.block_on_close:
if self._threads:
for thread in self._threads:
thread.join()

process_request returns immediately after starting the thread so the main serve_forever loop can accept the next connection without delay. process_request_thread calls finish_request (which instantiates the handler) inside a try/finally that always calls shutdown_request to close the connection socket, regardless of whether the handler raised. The block_on_close flag controls whether server_close waits for all in- flight threads; setting it to False yields a "fire and forget" mode where the interpreter may exit before all handlers finish.

StreamRequestHandler.setup (lines 730 to 800)

cpython 3.14 @ ab2d84fe1023/Lib/socketserver.py#L730-800

class StreamRequestHandler(BaseRequestHandler):
rbufsize = -1
wbufsize = 0
timeout = None
disable_nagle_algorithm = False

def setup(self):
self.connection = self.request
if self.timeout is not None:
self.connection.settimeout(self.timeout)
if self.disable_nagle_algorithm:
self.connection.setsockopt(IPPROTO_TCP,
TCP_NODELAY, True)
self.rfile = self.connection.makefile('rb', self.rbufsize)
if self.wbufsize == 0:
self.wfile = _SocketWriter(self.connection)
else:
self.wfile = self.connection.makefile('wb', self.wbufsize)

def finish(self):
if not self.wfile.closed:
try:
self.wfile.flush()
except socket.error:
pass
self.wfile.close()
self.rfile.close()

rbufsize=-1 (fully buffered) and wbufsize=0 (unbuffered) reflect typical stream usage: reads benefit from buffering because application code usually reads line-by-line, while writes must reach the peer promptly. When wbufsize=0, the write side uses the internal _SocketWriter helper rather than makefile to avoid the BufferedWriter's internal buffer entirely. disable_nagle_algorithm is for latency-sensitive protocols where small messages must not be coalesced by TCP.

gopy mirror

socketserver depends only on socket, selectors, os, sys, traceback, threading, and io. None of those have unusual requirements. The forking path (ForkingMixIn) calls os.fork() and os.waitpid(), which are Unix-only. The threading path is portable and is the likely first target for a gopy port.