Logging capture crashes when another thread registers a logger
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 78/100
Hướng nghiên cứu
Traceback trỏ đến _pytest/logging.py trong catching_logs.__enter__, nơi loggerDict.values() được duyệt. Hãy đọc mã thiết lập đó cùng các bài kiểm thử liên quan đến việc bắt log, sau đó tái hiện trường hợp đăng ký đồng thời trong issue. Hoàn tất khi việc thiết lập bắt log không còn phát sinh ngoại lệ trong lúc đăng ký logger đồng thời và có bài kiểm thử bao quát lỗi hồi quy.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
- 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
- Ngôn ngữ chính
- Python
- Star
- 14.6k
- Fork
- 3.5k
- Merge trung bình
- 2 ngày 6 giờ
- Pull request đã merge (30 ngày)
- 31
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của pytest-dev/pytest
-
Subtest durations become negative when `time` is mocked (subtests.py bypasses `_pytest.timing`)Có thể đã có người làm @RonnyPfannschmidt đã nhận 2 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
pytest-dev/pytest#15201 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
`faulthandler_timeout` stops covering a test once setup, call or a subtest fails (`pytest_exception_interact` cancels the timer)Có thể đã có người làm @mayur-dhavan đã nhận 2 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
pytest-dev/pytest#15180 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
monkeypatch.setattr fails to undo on objects with a custom __setattr__ (regression from #14969)Có thể đã có người làm @hardikkaurani đã nhận 33 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
pytest-dev/pytest#15099 · 6 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Allow dotted test filenames (`*.test.py`) via one-line change to `compute_module_name`Có thể làm lại được @siddy-stack đã nhận 70 ngày trước và không có pull request nào đang mở. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
pytest-dev/pytest#14514 · 6 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Expose CaptureManager as public APICó thể làm lại được @harsh21234i đã nhận 208 ngày trước và không có pull request nào đang mở. Đang mởtype: enhancement type: feature-branch
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
pytest-dev/pytest#14186 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của pytest-dev/pytest
Issue tương tự
-
backlog
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 91/100
EchoTools/nevr-runtime#1108 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
parse names a key after a top-level multi() threshold Bitcoin Core v31.1 refuses as not validĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
btclib-org/btclib-wallet#587 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
documentation
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Judgment-Pack/judgment-pack-spec#275 ·
Maintainer thường phản hồi trong vòng 1 ngày