Skip to content

Commit c42e9d7

Browse files
committed
Resolve SocketHandler's imports at construction, not eagerly
pickle, socket and struct go back to lazy. emit() can run during finalization, when importing no longer works, so SocketHandler.__init__ resolves them while it still can. Keeping them eager instead gives back three quarters of the handlers.py import-time win.
1 parent bafee5d commit c42e9d7

3 files changed

Lines changed: 22 additions & 11 deletions

File tree

‎Lib/logging/config.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,8 +513,7 @@ def _is_queue_like_object(obj):
513513
"""Check that *obj* implements the Queue API."""
514514
if isinstance(obj, (queue.Queue, queue.SimpleQueue)):
515515
return True
516-
# defer importing multiprocessing as much as possible; a lazy import at
517-
# module level would still be resolved by getmembers() and pydoc
516+
# defer importing multiprocessing as much as possible
518517
from multiprocessing.queues import Queue as MPQueue
519518
if isinstance(obj, MPQueue):
520519
return True

‎Lib/logging/handlers.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@
2626
import io # must stay eager to support finalization
2727
import logging
2828
import os
29-
import pickle
3029
import re
31-
import socket
32-
import struct
3330
import threading
3431
import time
3532
lazy import base64
3633
lazy import copy
3734
lazy import email.utils
3835
lazy import http.client
36+
lazy import pickle
3937
lazy import queue
4038
lazy import smtplib
39+
lazy import socket
40+
lazy import struct
4141
lazy import urllib.parse
4242
lazy from email.message import EmailMessage
4343

@@ -610,6 +610,8 @@ def __init__(self, host, port):
610610
self.retryStart = 1.0
611611
self.retryMax = 30.0
612612
self.retryFactor = 2.0
613+
# resolve what emit() needs now: it may run during finalization
614+
_ = pickle, socket, struct
613615

614616
def makeSocket(self, timeout=1):
615617
"""

‎Lib/test/test_logging.py‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,10 +1866,8 @@ def test_defaults_do_no_interpolation(self):
18661866
os.unlink(fn)
18671867

18681868
def test_names_from_handlers_module(self):
1869-
# gh-156777: "handlers.X" in a class or defaults entry is evaluated
1870-
# against vars(logging), so logging.handlers must be imported first.
1871-
# Run it in a subprocess, since importing this module already
1872-
# imports logging.handlers.
1869+
# gh-156777: "handlers.X" is evaluated against vars(logging). Use a
1870+
# subprocess: importing this module already imports logging.handlers.
18731871
ini = textwrap.dedent("""
18741872
[loggers]
18751873
keys=root
@@ -7619,10 +7617,22 @@ def test_lazy_imports_config(self):
76197617
def test_lazy_imports_handlers(self):
76207618
import_helper.ensure_lazy_imports(
76217619
"logging.handlers",
7622-
{"base64", "copy", "email", "http", "queue", "smtplib", "ssl",
7623-
"urllib"},
7620+
{"base64", "copy", "email", "http", "pickle", "queue", "smtplib",
7621+
"socket", "ssl", "struct", "urllib"},
76247622
)
76257623

7624+
def test_socket_handler_resolves_imports_when_created(self):
7625+
# gh-156777: emit() may run during finalization, when importing no
7626+
# longer works, so the handler resolves what it needs up front.
7627+
code = textwrap.dedent("""
7628+
import sys
7629+
import logging.handlers
7630+
logging.handlers.SocketHandler('localhost', 9020)
7631+
missing = {'pickle', 'socket', 'struct'} - sys.modules.keys()
7632+
assert not missing, missing
7633+
""")
7634+
assert_python_ok("-S", "-c", code)
7635+
76267636
def test_getmembers_without_ssl(self):
76277637
# gh-156777: getmembers() and pydoc resolve lazy imports, so a module
76287638
# level "lazy import ssl" would break them on a build without _ssl.

0 commit comments

Comments
 (0)