-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
Description
Bug report
The attached code below fairly reliably reproduces the deadlock.
There is a module level lock in logging/__init__.py. Each handler has its own lock also, implemented as an instance attribute.
Calling dictConfig (or fileConfig) locks them in this order:
- acquire module lock
- acquire handler locks in _clearExistingHandlers, which calls shutdown
Calling some logging functions from within a custom emit() can lock in this order:
- acquire handler lock (acquired before emit is called)
- acquire module lock (if
emitcalls into a library that useslogging.getLogger(), it will acquire the module lock)
The same issue exists with fileConfig. Note calling fileConfig during program execution is ok as per the docs: This function can be called several times from an application, allowing an end user to select from various pre-canned configurations (if the developer provides a mechanism to present the choices and load the chosen configuration).
While emit may not directly include code to logging.getLogger() it may call into another library that happens to do that. Even logger.isEnabledFor and logger.setLevel etc acquire the module lock and have the same issue.
Your environment
- CPython versions tested on: 3.8.13, 3.10.5
- Operating system and architecture: mac-os
Code to reproduce deadlock
import logging
import logging.config
import threading
import time
import os
def tick(msg):
print(time.strftime("%H:%M:%S"), msg)
class CustomHandler(logging.Handler):
def emit(self, record):
logging.getLogger("other-logger") # comment out this line to avoid deadlock
pass
def log_loop(i):
while True:
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.info("some log")
tick(f"tick from log_loop-{i}")
def config_loop():
while True:
config_once()
def config_once():
logging.config.dictConfig(
{
"version": 1,
"handlers": {"custom": {"class": "logging_deadlock.CustomHandler"}},
"root": {"handlers": ["custom"]},
}
)
tick(f"tick from config_once. pid: {os.getpid()}")
if __name__ == "__main__":
for i in range(10):
t = threading.Thread(target=log_loop, args=[i], name=f"log_loop-{i}")
t.start()
t2 = threading.Thread(target=config_loop, name="config_loop")
t2.start()
Call stacks that deadlocked
Thread 0x305833000 (idle): "log_loop-3"
_acquireLock (logging/__init__.py:226) # asking for the module lock
getLogger (logging/__init__.py:1329)
getLogger (logging/__init__.py:2079)
emit (logging_deadlock.py:16)
handle (logging/__init__.py:968) # this acquired the handler lock
callHandlers (logging/__init__.py:1696)
handle (logging/__init__.py:1634)
_log (logging/__init__.py:1624)
info (logging/__init__.py:1477)
log_loop (logging_deadlock.py:24)
run (threading.py:953)
_bootstrap_inner (threading.py:1016)
_bootstrap (threading.py:973)
Thread 0x30C848000 (idle): "config_loop"
acquire (logging/__init__.py:917) # asking for the handler lock
shutdown (logging/__init__.py:2181)
_clearExistingHandlers (logging/config.py:275)
configure (logging/config.py:544) # acquired the module lock
dictConfig (logging/config.py:810)
config_once (logging_deadlock.py:35)
config_loop (logging_deadlock.py:31)
run (threading.py:953)
_bootstrap_inner (threading.py:1016)
_bootstrap (threading.py:973)
Metadata
Metadata
Assignees
Labels
Projects
Status