Logging capture crashes when another thread registers a logger
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 78/100
Línea de trabajo
El traceback apunta a _pytest/logging.py en catching_logs.__enter__, donde se itera sobre loggerDict.values(). Lee ese código de configuración y las pruebas relacionadas con la captura de logs; después, reproduce el caso de registro concurrente del issue. El trabajo estará terminado cuando la configuración de la captura ya no lance una excepción durante el registro concurrente de loggers y una prueba cubra la regresión.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
- a detailed description of the bug or problem you are having
- output of
pip listfrom the virtual environment you are using - pytest and operating system versions
- minimal example if possible
Description
catching_logs.__enter__ can raise RuntimeError: dictionary changed size during iteration when another thread calls logging.getLogger() during capture setup.
The regression was introduced by #14375 / 7e6161c68579eb58ecceaf8c6b6f6b1b2f46c28a, which iterates the live logging.root.manager.loggerDict.values() view. logging.getLogger() mutates that dictionary while holding logging's registry lock, but pytest's iteration does not use the lock or take a snapshot.
Reproduction
import logging
import threading
from _pytest.logging import catching_logs
mutation_completed = threading.Event()
worker = None
class TriggerLogger(logging.Logger):
@property
def propagate(self):
global worker
def register_logger():
logging.getLogger("repro.concurrent-registration")
mutation_completed.set()
worker = threading.Thread(target=register_logger)
worker.start()
assert mutation_completed.wait(timeout=1)
return True
@propagate.setter
def propagate(self, value):
pass
manager = logging.root.manager
manager.loggerDict["repro.trigger"] = TriggerLogger("repro.trigger")
try:
with catching_logs(logging.NullHandler()):
pass
finally:
if worker is not None:
worker.join(timeout=1)
The property access happens after pytest's live dictionary iterator has yielded its first logger. The worker then uses the public logging.getLogger() API to mutate the registry before pytest requests the iterator's next value.
Actual result
File ".../_pytest/logging.py", line 366, in __enter__
for logger in root_logger.manager.loggerDict.values():
RuntimeError: dictionary changed size during iteration
Expected result
Concurrent logger registration should not make capture setup fail.
Suggested fix
Snapshot the registry before inspecting the loggers:
for logger in list(root_logger.manager.loggerDict.values()):
This also matches the behavior described in #14375: loggers created after capture setup starts do not need to be discovered by that capture scope.
Environment
- macOS 26.5.2 (Apple silicon)
- Python 3.13.13
- pytest 9.2.0.dev393+gc9bf1edf9 (
mainatc9bf1edf9e0d388f3e7bebc88576e608ffbc40a4) - Also reproduced with pytest 9.1.1 on Python 3.12
pip list
Package Version
------------------ ----------------------------
argcomplete 3.6.3
attrs 25.4.0
certifi 2026.1.4
charset-normalizer 3.4.4
coverage 7.13.1
elementpath 5.1.0
execnet 2.1.2
hypothesis 6.150.0
idna 3.11
iniconfig 2.3.0
mock 5.2.0
numpy 2.4.1
packaging 25.0
pexpect 4.9.0
pluggy 1.6.0
ptyprocess 0.7.0
Pygments 2.19.2
pytest 9.2.0.dev393+gc9bf1edf9
pytest-xdist 3.8.0
PyYAML 6.0.3
requests 2.32.5
setuptools 80.9.0
sortedcontainers 2.4.0
urllib3 2.6.3
xmlschema 4.3.0
- Lenguaje dominante
- Python
- Estrellas
- 14.6k
- Forks
- 3.5k
- Merge medio
- 3 d 2 h
- PR fusionados (30 d)
- 35
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de pytest-dev/pytest
-
Subtest durations become negative when `time` is mocked (subtests.py bypasses `_pytest.timing`)Posiblemente ocupada @RonnyPfannschmidt la tomó hace 3 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
pytest-dev/pytest#15201 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
`faulthandler_timeout` stops covering a test once setup, call or a subtest fails (`pytest_exception_interact` cancels the timer)Posiblemente ocupada @mayur-dhavan la tomó hace 2 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
pytest-dev/pytest#15180 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
monkeypatch.setattr fails to undo on objects with a custom __setattr__ (regression from #14969)Posiblemente ocupada @hardikkaurani la tomó hace 33 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
pytest-dev/pytest#15099 · 7 comentarios ·
Los mantenedores suelen responder en 1 día
-
Allow dotted test filenames (`*.test.py`) via one-line change to `compute_module_name`Quizá libre de nuevo @siddy-stack la tomó hace 71 días y no hay ningún pull request abierto. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
pytest-dev/pytest#14514 · 6 comentarios ·
Los mantenedores suelen responder en 1 día
-
Expose CaptureManager as public APIQuizá libre de nuevo @harsh21234i la tomó hace 209 días y no hay ningún pull request abierto. Abiertotype: enhancement type: feature-branch
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
pytest-dev/pytest#14186 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
Todos los issues de pytest-dev/pytest
Issues similares
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 72/100
521xueweihan/HelloGitHub#4150 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
aws-samples/sample-per-model-bedrock#595 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
Los mantenedores suelen responder en 1 día
-
bug connectors
Dificultad 1/5 Menos de una hora Aptitud para principiantes 67/100
pyinfra-dev/pyinfra#2036 ·
Los mantenedores suelen responder en 9 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día