Files
mlflow--mlflow/tests/utils/test_autologging_utils.py
2026-07-13 13:22:34 +08:00

96 lines
3.5 KiB
Python

import warnings
from threading import Thread
from mlflow import MlflowClient
from mlflow.entities import Metric
from mlflow.utils.autologging_utils.logging_and_warnings import (
_WarningsController,
)
from mlflow.utils.autologging_utils.metrics_queue import (
_metrics_queue,
_metrics_queue_lock,
flush_metrics_queue,
)
def test_flush_metrics_queue_is_thread_safe():
"""
Autologging augments TensorBoard event logging hooks with MLflow `log_metric` API
calls. To prevent these API calls from blocking TensorBoard event logs, `log_metric`
API calls are scheduled via `_flush_queue` on a background thread. Accordingly, this test
verifies that `_flush_queue` is thread safe.
"""
client = MlflowClient()
run = client.create_run(experiment_id="0")
metric_queue_item = (run.info.run_id, Metric("foo", 0.1, 100, 1))
_metrics_queue.append(metric_queue_item)
# Verify that, if another thread holds a lock on the metric queue leveraged by
# _flush_queue, _flush_queue terminates and does not modify the queue
_metrics_queue_lock.acquire()
flush_thread1 = Thread(name="test-autologging-flush-1", target=flush_metrics_queue)
flush_thread1.start()
flush_thread1.join()
assert len(_metrics_queue) == 1
assert _metrics_queue[0] == metric_queue_item
_metrics_queue_lock.release()
# Verify that, if no other thread holds a lock on the metric queue leveraged by
# _flush_queue, _flush_queue flushes the queue as expected
flush_thread2 = Thread(name="test-autologging-flush-2", target=flush_metrics_queue)
flush_thread2.start()
flush_thread2.join()
assert len(_metrics_queue) == 0
def test_double_patch_does_not_overwrite(monkeypatch):
original_showwarning = warnings.showwarning
monkeypatch.setattr(warnings, "showwarning", original_showwarning)
controller = _WarningsController()
assert warnings.showwarning == original_showwarning
assert not controller._did_patch_showwarning
controller.set_non_mlflow_warnings_disablement_state_for_current_thread(True)
assert controller._did_patch_showwarning
assert warnings.showwarning == controller._patched_showwarning
patched_func = warnings.showwarning
controller._modify_patch_state_if_necessary()
assert warnings.showwarning == patched_func
controller.set_non_mlflow_warnings_disablement_state_for_current_thread(False)
assert warnings.showwarning == original_showwarning
def test_showwarning_captures_user_handler():
"""Verify that a user-set warnings.showwarning handler survives the patch/unpatch cycle.
Regression test for https://github.com/mlflow/mlflow/issues/21689
"""
original_showwarning = warnings.showwarning
# Simulate a user setting a custom handler after import (e.g. logging.captureWarnings)
custom_handler = lambda *args, **kwargs: None # noqa: E731
warnings.showwarning = custom_handler
try:
controller = _WarningsController()
# Patch: controller should capture the user's custom handler
controller.set_non_mlflow_warnings_disablement_state_for_current_thread(True)
assert warnings.showwarning == controller._patched_showwarning
assert controller._original_showwarning is custom_handler
# Unpatch: controller should restore the user's custom handler, not the default
controller.set_non_mlflow_warnings_disablement_state_for_current_thread(False)
assert warnings.showwarning is custom_handler
finally:
warnings.showwarning = original_showwarning