fix(runtime): distinguish event loop owners (#6425)

Co-authored-by: jxxghp <jxxghp@gmail.com>
This commit is contained in:
InfinityPacer
2026-08-23 21:40:23 +08:00
committed by GitHub
co-authored by jxxghp
parent f64030a074
commit eb36e6be91
4 changed files with 76 additions and 17 deletions
+22 -3
View File
@@ -47,14 +47,33 @@ def test_clear_global_loop_preserves_new_owner() -> None:
async def verify() -> None:
current = asyncio.get_running_loop()
runtime.set_loop(current)
runtime.clear_loop(previous)
previous_owner = runtime.set_loop(previous)
current_owner = runtime.set_loop(current)
runtime.clear_loop(previous_owner)
assert runtime.loop is current
runtime.clear_loop(current)
runtime.clear_loop(current_owner)
assert runtime.CURRENT_EVENT_LOOP is None
try:
asyncio.run(verify())
finally:
previous.close()
def test_nested_owner_release_restores_same_event_loop() -> None:
"""同一循环上的内层生命周期退出后,外层 owner 仍保持登记。"""
runtime = GlobalVar()
async def verify() -> None:
loop = asyncio.get_running_loop()
outer_owner = runtime.set_loop(loop)
inner_owner = runtime.set_loop(loop)
runtime.clear_loop(inner_owner)
assert runtime.loop is loop
runtime.clear_loop(outer_owner)
assert runtime.CURRENT_EVENT_LOOP is None
asyncio.run(verify())
+26 -4
View File
@@ -152,8 +152,9 @@ def test_lifespan_normal_mode_starts_full_runtime(monkeypatch):
asyncio.run(run_lifespan())
configured_loop = lifecycle.global_vars.set_loop.call_args.args[0]
lifecycle.global_vars.clear_loop.assert_called_once_with(configured_loop)
lifecycle.global_vars.clear_loop.assert_called_once_with(
lifecycle.global_vars.set_loop.return_value
)
lifecycle.init_modules.assert_awaited_once_with()
lifecycle.prepare_database_component.assert_called_once()
lifecycle.configure_plugin_services.assert_called_once_with()
@@ -170,6 +171,26 @@ def test_lifespan_normal_mode_starts_full_runtime(monkeypatch):
_assert_completed_once(step)
def test_lifespan_validation_failure_does_not_clear_outer_loop_owner(monkeypatch):
"""当前生命周期尚未取得 owner 时,启动失败不得清理外层登记。"""
_patch_lifespan(monkeypatch)
monkeypatch.setattr(
lifecycle,
"validate_process_topology",
MagicMock(side_effect=RuntimeError("invalid topology")),
)
async def run_lifespan():
async with lifecycle.lifespan(FastAPI()):
pass
with pytest.raises(RuntimeError, match="invalid topology"):
asyncio.run(run_lifespan())
lifecycle.global_vars.set_loop.assert_not_called()
lifecycle.global_vars.clear_loop.assert_not_called()
@pytest.mark.parametrize(
("failing_step", "completed_steps", "blocked_steps"),
[
@@ -741,8 +762,9 @@ def test_lifespan_fails_fast_when_async_engine_cannot_be_built(monkeypatch):
with pytest.raises(RuntimeError, match="no async driver"):
asyncio.run(run_lifespan())
configured_loop = lifecycle.global_vars.set_loop.call_args.args[0]
lifecycle.global_vars.clear_loop.assert_called_once_with(configured_loop)
lifecycle.global_vars.clear_loop.assert_called_once_with(
lifecycle.global_vars.set_loop.return_value
)
# 失败要发生在任何东西被初始化之前,否则模块起来了却没人关:关停块在 yield 处才开始
lifecycle.init_routers.assert_not_called()
lifecycle.init_modules.assert_not_called()