From 2cb04b90290df2c6a6fce4fdeb8e94f434e3a90c Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:28:11 +0200 Subject: [PATCH] [3.14] gh-73458: Fix logging.config.listen() on a host without an IPv4 address (GH-154491) (GH-154617) The server is created in a thread which set the "ready" event only after a successful start, so a failure to start left the caller waiting for that event forever. Set it also on failure. The receiver always used AF_INET, which fails if the host has no IPv4 address, for example if "localhost" is only aliased to ::1. Use the family of the first resolved address in such case. Also fixes gh-82076. (cherry picked from commit 45a10f5d711ebe1bce9c6b93c10bbc5fe5bdf392) Co-authored-by: Serhiy Storchaka Co-authored-by: Claude Opus 4.8 (1M context) --- Lib/logging/config.py | 21 ++++++++++++++++--- Lib/test/test_logging.py | 20 ++++++++++++++---- ...6-07-22-13-01-24.gh-issue-73458.byxSPi.rst | 5 +++++ 3 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-22-13-01-24.gh-issue-73458.byxSPi.rst diff --git a/Lib/logging/config.py b/Lib/logging/config.py index 3d9aa00fa52d..e0524eb5e3d8 100644 --- a/Lib/logging/config.py +++ b/Lib/logging/config.py @@ -32,6 +32,7 @@ import logging.handlers import os import queue import re +import socket import struct import threading import traceback @@ -1022,6 +1023,15 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None): def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, handler=None, ready=None, verify=None): + # The host can have no IPv4 address, for example if "localhost" + # is only aliased to ::1. Leave resolution errors to the server. + try: + infos = socket.getaddrinfo(host, port, type=socket.SOCK_STREAM) + except OSError: + pass + else: + if not any(info[0] == socket.AF_INET for info in infos): + self.address_family = infos[0][0] ThreadingTCPServer.__init__(self, (host, port), handler) with logging._lock: self.abort = 0 @@ -1053,9 +1063,14 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None): self.ready = threading.Event() def run(self): - server = self.rcvr(port=self.port, handler=self.hdlr, - ready=self.ready, - verify=self.verify) + try: + server = self.rcvr(port=self.port, handler=self.hdlr, + ready=self.ready, + verify=self.verify) + except BaseException: + # Do not leave the caller waiting for ready forever. + self.ready.set() + raise if self.port == 0: self.port = server.server_address[1] self.ready.set() diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index c1dc06e02d96..0c27c03ac850 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -3634,14 +3634,14 @@ class ConfigDictTest(BaseTest): # Ask for a randomly assigned port (by using port 0) t = logging.config.listen(0, verify) t.start() - t.ready.wait() + self.assertTrue(t.ready.wait(support.LONG_TIMEOUT), + msg='the listener did not start') # Now get the port allocated port = t.port t.ready.clear() try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(2.0) - sock.connect(('localhost', port)) + # The server can listen on IPv6, so do not force a family. + sock = socket.create_connection(('localhost', port), timeout=2.0) slen = struct.pack('>L', len(text)) s = slen + text @@ -3756,6 +3756,18 @@ class ConfigDictTest(BaseTest): ('ERROR', '2'), ], pat=r"^[\w.]+ -> (\w+): (\d+)$") + @support.requires_working_socket() + def test_listen_server_error(self): + # The "ready" event should be set even if the server fails to start. + t = logging.config.listen(-1) + t.daemon = True + with threading_helper.catch_threading_exception() as cm: + t.start() + self.assertTrue(t.ready.wait(support.SHORT_TIMEOUT), + msg='the listener did not report the failure') + threading_helper.join_thread(t) + self.assertIs(cm.exc_type, OverflowError) + def test_bad_format(self): self.assertRaises(ValueError, self.apply_config, self.bad_format) diff --git a/Misc/NEWS.d/next/Library/2026-07-22-13-01-24.gh-issue-73458.byxSPi.rst b/Misc/NEWS.d/next/Library/2026-07-22-13-01-24.gh-issue-73458.byxSPi.rst new file mode 100644 index 000000000000..27b6eeac5b52 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-22-13-01-24.gh-issue-73458.byxSPi.rst @@ -0,0 +1,5 @@ +Fix :func:`logging.config.listen`: it left the caller waiting for the +``ready`` event forever if the server could not be started, +for example if the port was invalid or already in use. +It now also binds to an IPv6 address if the host has no IPv4 address, +for example if ``localhost`` is only aliased to ``::1``. -- 2.47.3