Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 34 additions & 15 deletions flocks/cli/service_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1045,15 +1045,42 @@ def _run_legacy_task_migration(root: Path, console) -> None:
console.print("[flocks] 旧任务迁移失败,请检查日志。")


def _resolve_stop_ports(
paths: RuntimePaths,
config: ServiceConfig | None = None,
) -> tuple[int, int]:
"""Resolve frontend/backend ports for stop flows.

When a runtime record is missing or uses the legacy pid-only format,
``start`` and ``restart`` should fall back to the current CLI config
rather than the static default ports.
"""
frontend_default = config.frontend_port if config is not None else ServiceConfig.frontend_port
backend_default = config.backend_port if config is not None else ServiceConfig.backend_port
return (
_effective_frontend_port(paths, frontend_default),
_recorded_port(paths.backend_pid, backend_default),
)


def _stop_all_locked(
paths: RuntimePaths,
console,
*,
config: ServiceConfig | None = None,
) -> None:
"""Stop frontend then backend while reusing the caller's lock."""
fe_port, be_port = _resolve_stop_ports(paths, config)
_resolve_upgrade_runtime(console, frontend_port=fe_port, attempt_recover=False)
stop_one(fe_port, paths.frontend_pid, "WebUI", console)
stop_one(be_port, paths.backend_pid, "后端", console)


def stop_all(console) -> None:
"""Stop frontend then backend using ports persisted in runtime records."""
paths = ensure_runtime_dirs()
with service_lock(paths):
fe_port = _effective_frontend_port(paths, ServiceConfig.frontend_port)
be_port = _recorded_port(paths.backend_pid, ServiceConfig.backend_port)
_resolve_upgrade_runtime(console, frontend_port=fe_port, attempt_recover=False)
stop_one(fe_port, paths.frontend_pid, "WebUI", console)
stop_one(be_port, paths.backend_pid, "后端", console)
_stop_all_locked(paths, console)


def _start_all_without_stop(config: ServiceConfig, console) -> None:
Expand All @@ -1070,23 +1097,15 @@ def start_all(config: ServiceConfig, console) -> None:
"""Ensure backend and frontend are restarted with a clean state."""
paths = ensure_runtime_dirs()
with service_lock(paths):
fe_port = _effective_frontend_port(paths, config.frontend_port)
be_port = _recorded_port(paths.backend_pid, ServiceConfig.backend_port)
_resolve_upgrade_runtime(console, frontend_port=fe_port, attempt_recover=False)
stop_one(fe_port, paths.frontend_pid, "WebUI", console)
stop_one(be_port, paths.backend_pid, "后端", console)
_stop_all_locked(paths, console, config=config)
_start_all_without_stop(config, console)


def restart_all(config: ServiceConfig, console) -> None:
"""Restart backend and frontend."""
paths = ensure_runtime_dirs()
with service_lock(paths):
fe_port = _effective_frontend_port(paths, config.frontend_port)
be_port = _recorded_port(paths.backend_pid, ServiceConfig.backend_port)
_resolve_upgrade_runtime(console, frontend_port=fe_port, attempt_recover=False)
stop_one(fe_port, paths.frontend_pid, "WebUI", console)
stop_one(be_port, paths.backend_pid, "后端", console)
_stop_all_locked(paths, console, config=config)
_start_all_without_stop(config, console)


Expand Down
65 changes: 65 additions & 0 deletions tests/cli/test_service_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,71 @@ def test_restart_all_stops_then_starts_under_lock(monkeypatch) -> None:
]


def test_start_all_uses_config_backend_port_when_pid_record_missing(monkeypatch, tmp_path: Path) -> None:
paths = service_manager.RuntimePaths(
root=tmp_path,
run_dir=tmp_path / "run",
log_dir=tmp_path / "logs",
backend_pid=tmp_path / "run" / "backend.pid",
frontend_pid=tmp_path / "run" / "webui.pid",
backend_log=tmp_path / "logs" / "backend.log",
frontend_log=tmp_path / "logs" / "webui.log",
)
paths.run_dir.mkdir(parents=True)
config = service_manager.ServiceConfig(frontend_port=5175, backend_port=9100)
calls: list[tuple[int, Path, str]] = []

monkeypatch.setattr(service_manager, "ensure_runtime_dirs", lambda: paths)
monkeypatch.setattr(service_manager, "service_lock", lambda _paths: _record_call([], "service_lock"))
monkeypatch.setattr(service_manager, "_resolve_upgrade_runtime", lambda *_args, **_kwargs: None)
monkeypatch.setattr(
service_manager,
"stop_one",
lambda port, pid_file, name, _console: calls.append((port, pid_file, name)),
)
monkeypatch.setattr(service_manager, "_start_all_without_stop", lambda *_args, **_kwargs: None)

service_manager.start_all(config, console=None)

assert calls == [
(5175, paths.frontend_pid, "WebUI"),
(9100, paths.backend_pid, "后端"),
]


def test_restart_all_uses_config_backend_port_with_legacy_pid_record(monkeypatch, tmp_path: Path) -> None:
paths = service_manager.RuntimePaths(
root=tmp_path,
run_dir=tmp_path / "run",
log_dir=tmp_path / "logs",
backend_pid=tmp_path / "run" / "backend.pid",
frontend_pid=tmp_path / "run" / "webui.pid",
backend_log=tmp_path / "logs" / "backend.log",
frontend_log=tmp_path / "logs" / "webui.log",
)
paths.run_dir.mkdir(parents=True)
paths.backend_pid.write_text("12345", encoding="utf-8")
config = service_manager.ServiceConfig(frontend_port=5176, backend_port=9200)
calls: list[tuple[int, Path, str]] = []

monkeypatch.setattr(service_manager, "ensure_runtime_dirs", lambda: paths)
monkeypatch.setattr(service_manager, "service_lock", lambda _paths: _record_call([], "service_lock"))
monkeypatch.setattr(service_manager, "_resolve_upgrade_runtime", lambda *_args, **_kwargs: None)
monkeypatch.setattr(
service_manager,
"stop_one",
lambda port, pid_file, name, _console: calls.append((port, pid_file, name)),
)
monkeypatch.setattr(service_manager, "_start_all_without_stop", lambda *_args, **_kwargs: None)

service_manager.restart_all(config, console=None)

assert calls == [
(5176, paths.frontend_pid, "WebUI"),
(9200, paths.backend_pid, "后端"),
]


def test_start_all_stops_on_failure_before_restart(monkeypatch) -> None:
paths = service_manager.RuntimePaths(
root=Path("/tmp"),
Expand Down
Loading