Skip to content
Open
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9aac18d
Refactor of _from_server_socket()
julianz- Apr 7, 2026
b469389
Simply the socket OS error handling
julianz- Apr 7, 2026
4341144
Move the wrap() operation into a separate helper
julianz- Apr 7, 2026
0f28843
refactor _from_server_socket exception handler
julianz- Apr 7, 2026
526c291
Move socket acceptance into separate helper
julianz- Apr 7, 2026
63a63ad
Add socket configuration helper
julianz- Apr 7, 2026
45b26ef
Simplify _from_server_socket into two steps
julianz- Apr 7, 2026
9524853
Added IPv4 and IPv6 to spell check dicitonary
julianz- Apr 7, 2026
3b39b17
Added towncrier statement for whole refactor
julianz- Apr 7, 2026
de7bee4
Added tests for refactored code to improve coverage
julianz- Apr 7, 2026
be92e4b
Fix test_tls_client_auth for Windows 2025
julianz- Apr 9, 2026
57031ff
Updated tests for from_server_socket
julianz- Apr 11, 2026
6892f6b
Fix socket leak in _wrap_socket_for_tls on FatalSSLAlert
julianz- Jun 19, 2026
5a26b85
Rename _ignore_socket_oserror to _is_ignorable_socket_error
julianz- Jun 19, 2026
046a870
Replace sentinel tuple returns with exceptions in _prepare_socket
julianz- Jun 19, 2026
0348719
Use full variable names and positional-only args in socket methods
julianz- Jun 19, 2026
6a2809c
Use InterruptedError instead of OSError(EINTR) in transport error test
julianz- Jun 20, 2026
809dd22
Simplify transport error test by removing unnecessary pipe_fd fixture
julianz- Jun 20, 2026
01fde84
Document socket.timeout as OSError subclass and TimeoutError alias
julianz- Jun 20, 2026
050a198
Fix changelog fragment formatting
julianz- Jun 20, 2026
96aa39f
Rephrase _prepare_socket docstring to avoid spell check failures
julianz- Jun 20, 2026
2b63a58
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 20, 2026
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
71 changes: 41 additions & 30 deletions cheroot/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,44 @@ def _remove_invalid_sockets(self):
with _cm.suppress(OSError):
conn.close()

def _setup_conn_addr(self, conn, s, addr):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use full words for variable names (things like i/j/k/s aren't really descriptive, nor are the shortened words). It'd also be a good idea to settle for all the args being keyword-only or positional-only (seems like in this case positional args of different types make up bad DX).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"""Configure remote address and port for the connection.

Populates the connection object with remote address metadata.
If the address is unavailable following the handshake, this
method detects the socket's address family (IPv4/IPv6)
and provides an appropriate 'any' address placeholder.
"""
# optional values
# Until we do DNS lookups, omit REMOTE_HOST
if addr is None: # sometimes this can happen
# figure out if AF_INET or AF_INET6.
if len(s.getsockname()) == 2:
# AF_INET
addr = ('0.0.0.0', 0)
else:
# AF_INET6
addr = ('::', 0)
conn.remote_addr = addr[0]
conn.remote_port = addr[1]

def _ignore_socket_oserror(self, exc):
if self.server.stats['Enabled']:
self.server.stats['Socket Errors'] += 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably belongs outside a function saying "ignore". Though, maybe the helper could be renamed since it doesn't actually ignore anything but checks for expected exceptions.

How about something like def _is_suppressable_exc(exc: OSError, /) -> bool:? OTOH, could you check if specific OSError subclasses aren't being raised? Maybe, we could replace checking the low-level codes with suppressing the corresponding specific exceptions like InterruptedError and similar?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, though I prefer the slightly more intuitive _is_ignorable_socket_error() as the name. I added the / but since none of the other functions have type annotations I left those out β€” maybe that's better done in a separate PR?

err_code = exc.args[0]

# EINTR should occur when a signal is received during accept();
# retry the call. See https://github.com/cherrypy/cherrypy/issues/707.
is_eintr = err_code in errors.socket_error_eintr

# Just try again. See https://github.com/cherrypy/cherrypy/issues/479.
is_nonblocking = err_code in errors.socket_errors_nonblocking

# Our socket was closed. See https://github.com/cherrypy/cherrypy/issues/686.
is_ignored = err_code in errors.socket_errors_to_ignore

return is_eintr or is_nonblocking or is_ignored

def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
try:
s, addr = server_socket.accept()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why can't we wrap this line with a suppression of a single exception instead of handling a bunch of errors that could come from a lot of arbitrary places within the try-block..

@julianz- julianz- Jun 19, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to handle two different types of exception - _NoConnectionAvailable, ConnectionError. We keep this separation so that transient accept failures (no connection ready) are kept separate from TLS handshake failures.

Expand Down Expand Up @@ -335,18 +373,7 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
conn = self.server.ConnectionClass(self.server, s, mf)

if not isinstance(self.server.bind_addr, (str, bytes)):
# optional values
# Until we do DNS lookups, omit REMOTE_HOST
if addr is None: # sometimes this can happen
# figure out if AF_INET or AF_INET6.
if len(s.getsockname()) == 2:
# AF_INET
addr = ('0.0.0.0', 0)
else:
# AF_INET6
addr = ('::', 0)
conn.remote_addr = addr[0]
conn.remote_port = addr[1]
self._setup_conn_addr(conn, s, addr)

conn.ssl_env = ssl_env
return conn
Expand All @@ -356,24 +383,8 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
# notice keyboard interrupts on Win32, which don't interrupt
# accept() by default
return None
except OSError as ex:
if self.server.stats['Enabled']:
self.server.stats['Socket Errors'] += 1
if ex.args[0] in errors.socket_error_eintr:
# I *think* this is right. EINTR should occur when a signal
# is received during the accept() call; all docs say retry
# the call, and I *think* I'm reading it right that Python
# will then go ahead and poll for and handle the signal
# elsewhere. See
# https://github.com/cherrypy/cherrypy/issues/707.
return None
if ex.args[0] in errors.socket_errors_nonblocking:
# Just try again. See
# https://github.com/cherrypy/cherrypy/issues/479.
return None
if ex.args[0] in errors.socket_errors_to_ignore:
# Our socket was closed.
# See https://github.com/cherrypy/cherrypy/issues/686.
except OSError as exc:
if self._ignore_socket_oserror(exc):
return None
raise

Expand Down