Skip to content

gh-156378: Stop logging.config.listen() spinning on a truncated request - #156391

Open
ayaangazali wants to merge 1 commit into
python:mainfrom
ayaangazali:gh-156378-listen-eof-spin
Open

gh-156378: Stop logging.config.listen() spinning on a truncated request#156391
ayaangazali wants to merge 1 commit into
python:mainfrom
ayaangazali:gh-156378-listen-eof-spin

Conversation

@ayaangazali

@ayaangazali ayaangazali commented Aug 25, 2026

Copy link
Copy Markdown

ConfigStreamHandler.handle() reads a 4-byte length and then loops until that many bytes have arrived:

chunk = self.connection.recv(slen)
while len(chunk) < slen:
    chunk = chunk + conn.recv(slen - len(chunk))

At end of stream recv() returns b'', so chunk stops growing while the loop condition stays true. A client that announces a length and then closes without sending the body leaves the handler calling recv() forever. verify cannot help, since it only runs after this loop.

This treats an empty read as end of stream and drops the partial request, which matches what the handler does with anything else it cannot act on.

Verifying

Announce one byte, then hang up, and watch process CPU while otherwise idle:

t = logging.config.listen(0)
t.start(); t.ready.wait(5)
sock = socket.create_connection(('localhost', t.port), timeout=2.0)
sock.sendall(struct.pack('>L', 1))
sock.close()
cpu0 = time.process_time(); time.sleep(0.8); cpu1 = time.process_time()
print(cpu1 - cpu0)

Before: 0.805 seconds of CPU over 0.8 seconds of wall clock, so a core is pinned. After: 0.001.

test_logging passes in full, 292 tests.

On the test

The added test waits for the handler thread to exit rather than measuring CPU, because a CPU threshold would be flaky on a loaded machine. It only fails if the thread never exits, so a slow box just makes it slower, not red. Verified both directions: with Lib/logging/config.py reverted it fails with AssertionError: the request handler did not exit after EOF, and it passes with the fix.

I did not try to make the handler report the truncated request. It looked like a separate behaviour question from stopping the spin, and the surrounding code already discards input it cannot use without comment.

sorry if I've missed something here. I reproduced it from the issue, traced the loop myself, and used Claude Code to sanity check the fix and the test design before settling on waiting for thread exit instead of timing CPU. happy to change any of it. freshman in college, still learning my way around this codebase :)

… request

ConfigStreamHandler.handle() reads a 4-byte length and then loops until
that many bytes arrive. At EOF recv() returns b'', so a client that
announces a length and then closes without sending the body left the loop
calling recv() forever, burning a CPU core per connection.

Treat an empty read as end of stream and drop the partial request.
@ayaangazali
ayaangazali requested a review from vsajip as a code owner August 25, 2026 23:25
Copilot AI lite review requested due to automatic review settings August 25, 2026 23:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants