import os
import queue
import re
+import socket
import struct
import threading
import traceback
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
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()
# 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
('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)
--- /dev/null
+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``.