Skip to content
Open
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
7 changes: 6 additions & 1 deletion Lib/logging/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,12 @@ def handle(self):
slen = struct.unpack(">L", chunk)[0]
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + conn.recv(slen - len(chunk))
data = conn.recv(slen - len(chunk))
if not data:
# The peer closed before sending the whole
# configuration, so there is nothing to apply.
return
chunk = chunk + data
if self.server.verify is not None:
chunk = self.server.verify(chunk)
if chunk is not None: # verified, can process
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3740,6 +3740,35 @@ def setup_via_listener(self, text, verify=None):
logging.config.stopListening()
threading_helper.join_thread(t)

@support.requires_working_socket()
def test_listen_truncated_request_does_not_spin(self):
# gh-156378: a client that declares a length and then closes without
# sending the body used to leave handle() calling recv() forever,
# burning a CPU core, because recv() keeps returning b'' at EOF.
t = logging.config.listen(0)
t.start()
self.assertTrue(t.ready.wait(support.LONG_TIMEOUT),
msg='the listener did not start')
port = t.port
t.ready.clear()
before = threading.active_count()
try:
sock = socket.create_connection(('localhost', port), timeout=2.0)
# Announce one byte of configuration, then hang up without it.
sock.sendall(struct.pack('>L', 1))
sock.close()
# The handler must notice EOF and finish. Wait rather than assert
# immediately, so a slow machine does not make this flaky; only a
# handler that never exits fails here.
deadline = time.monotonic() + support.SHORT_TIMEOUT
while threading.active_count() > before:
if time.monotonic() > deadline:
self.fail('the request handler did not exit after EOF')
time.sleep(0.01)
finally:
logging.config.stopListening()
threading_helper.join_thread(t)

@support.requires_working_socket()
def test_listen_config_10_ok(self):
with support.captured_stdout() as output:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :func:`logging.config.listen` looping forever and consuming a CPU core
when a client announces a configuration length and then closes the connection
without sending the data.
Loading