1+ # SPDX-License-Identifier: Apache-2.0
2+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
4+ from types import SimpleNamespace
5+ from unittest .mock import MagicMock
6+
7+ import pytest
8+
9+ import vllm .v1 .engine .core_client as core_client_mod
10+ import vllm .v1 .engine .llm_engine as llm_engine_mod
11+ from vllm .v1 .engine .core_client import MPClient
12+ from vllm .v1 .engine .llm_engine import LLMEngine
13+
14+ pytestmark = pytest .mark .skip_global_cleanup
15+
16+
17+ class DummyResources :
18+
19+ def __init__ (self , * , engine_dead : bool , engine_manager ):
20+ self .engine_dead = engine_dead
21+ self .engine_manager = engine_manager
22+ self .cleanup = MagicMock ()
23+
24+ def __call__ (self ):
25+ self .cleanup ()
26+
27+
28+ def test_mp_client_shutdown_marks_engine_dead_before_manager_shutdown ():
29+ client = object .__new__ (MPClient )
30+ client ._finalizer = MagicMock ()
31+ client ._finalizer .detach .return_value = object ()
32+
33+ engine_manager = MagicMock ()
34+ client .resources = DummyResources (
35+ engine_dead = False ,
36+ engine_manager = engine_manager ,
37+ )
38+
39+ client .shutdown (timeout = 3.0 )
40+
41+ assert client .resources .engine_dead is True
42+ engine_manager .shutdown .assert_called_once_with (timeout = 3.0 )
43+ client .resources .cleanup .assert_called_once_with ()
44+
45+
46+ def test_mp_client_monitor_ignores_clean_engine_exit (monkeypatch : pytest .MonkeyPatch ):
47+ client = object .__new__ (MPClient )
48+ client ._finalizer = SimpleNamespace (alive = True )
49+ client .resources = SimpleNamespace (
50+ engine_dead = False ,
51+ engine_manager = SimpleNamespace (
52+ failed_proc_name = None ,
53+ monitor_engine_liveness = lambda : None ,
54+ ),
55+ )
56+ client .shutdown = MagicMock ()
57+
58+ thread_target = None
59+
60+ class ImmediateThread :
61+ def __init__ (self , * , target , daemon , name ):
62+ nonlocal thread_target
63+ thread_target = target
64+
65+ def start (self ):
66+ thread_target ()
67+
68+ monkeypatch .setattr (core_client_mod , "Thread" , ImmediateThread )
69+ logger_error = MagicMock ()
70+ monkeypatch .setattr (core_client_mod .logger , "error" , logger_error )
71+
72+ client .start_engine_core_monitor ()
73+
74+ assert client .resources .engine_dead is False
75+ client .shutdown .assert_not_called ()
76+ logger_error .assert_not_called ()
77+
78+
79+ def test_llm_engine_shutdown_cleans_up_owned_resources (
80+ monkeypatch : pytest .MonkeyPatch ,
81+ ):
82+ llm_engine = object .__new__ (LLMEngine )
83+ renderer = MagicMock ()
84+ engine_core = MagicMock ()
85+ dp_group = object ()
86+ llm_engine .renderer = renderer
87+ llm_engine .engine_core = engine_core
88+ llm_engine .dp_group = dp_group
89+ llm_engine .external_launcher_dp = False
90+
91+ shutdown_prometheus = MagicMock ()
92+ destroy_dp_group = MagicMock ()
93+ monkeypatch .setattr (llm_engine_mod , "shutdown_prometheus" , shutdown_prometheus )
94+ monkeypatch .setattr (
95+ llm_engine_mod ,
96+ "stateless_destroy_torch_distributed_process_group" ,
97+ destroy_dp_group ,
98+ )
99+
100+ llm_engine .shutdown (timeout = 1.5 )
101+
102+ shutdown_prometheus .assert_called_once_with ()
103+ renderer .shutdown .assert_called_once_with ()
104+ engine_core .shutdown .assert_called_once_with (timeout = 1.5 )
105+ destroy_dp_group .assert_called_once_with (dp_group )
106+ assert llm_engine .renderer is None
107+ assert llm_engine .engine_core is None
108+ assert llm_engine .dp_group is None
0 commit comments