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
21 changes: 18 additions & 3 deletions Lib/logging/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import os
import queue
import re
import socket
import struct
import threading
import traceback
Expand Down Expand Up @@ -1004,6 +1005,15 @@ class ConfigSocketReceiver(ThreadingTCPServer):

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
Expand Down Expand Up @@ -1035,9 +1045,14 @@ def __init__(self, rcvr, hdlr, port, verify):
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()
Expand Down
20 changes: 16 additions & 4 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3643,14 +3643,14 @@ def setup_via_listener(self, text, verify=None):
# 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
Expand Down Expand Up @@ -3765,6 +3765,18 @@ def verify_reverse(stuff):
('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)

Expand Down
Original file line number Diff line number Diff line change
@@ -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``.
Loading