mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-15 23:46:51 +08:00
183 lines
7.5 KiB
Python
183 lines
7.5 KiB
Python
# ========================================================================== #
|
|
# #
|
|
# KVMD - The main PiKVM daemon. #
|
|
# #
|
|
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
|
# #
|
|
# This program is free software: you can redistribute it and/or modify #
|
|
# it under the terms of the GNU General Public License as published by #
|
|
# the Free Software Foundation, either version 3 of the License, or #
|
|
# (at your option) any later version. #
|
|
# #
|
|
# This program is distributed in the hope that it will be useful, #
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
|
# GNU General Public License for more details. #
|
|
# #
|
|
# You should have received a copy of the GNU General Public License #
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
|
# #
|
|
# ========================================================================== #
|
|
|
|
|
|
import asyncio
|
|
import asyncio.subprocess
|
|
|
|
from ....logging import get_logger
|
|
|
|
from .... import tools
|
|
from .... import aiotools
|
|
from .... import aioproc
|
|
|
|
|
|
# =====
|
|
class Runner: # pylint: disable=too-many-instance-attributes
|
|
def __init__(
|
|
self,
|
|
reset_delay: float,
|
|
shutdown_delay: float,
|
|
|
|
pre_start_cmd: list[str],
|
|
cmd: list[str],
|
|
post_stop_cmd: list[str],
|
|
) -> None:
|
|
|
|
self.__reset_delay = reset_delay
|
|
self.__shutdown_delay = shutdown_delay
|
|
|
|
self.__pre_start_cmd: list[str] = pre_start_cmd
|
|
self.__cmd: list[str] = cmd
|
|
self.__post_stop_cmd: list[str] = post_stop_cmd
|
|
|
|
self.__proc_params: dict = {}
|
|
self.__proc_task: (asyncio.Task | None) = None
|
|
self.__proc: (asyncio.subprocess.Process | None) = None # pylint: disable=no-member
|
|
|
|
self.__stopper_task: (asyncio.Task | None) = None
|
|
self.__stopper_wip = False
|
|
|
|
@aiotools.atomic_fg
|
|
async def ensure_start(self, params: dict) -> None:
|
|
if not self.__proc_task or self.__stopper_task:
|
|
logger = get_logger(0)
|
|
|
|
if self.__stopper_task:
|
|
if not self.__stopper_wip:
|
|
self.__stopper_task.cancel()
|
|
await asyncio.gather(self.__stopper_task, return_exceptions=True)
|
|
logger.info("Streamer stop cancelled")
|
|
return
|
|
else:
|
|
await asyncio.gather(self.__stopper_task, return_exceptions=True)
|
|
|
|
logger.info("Starting streamer ...")
|
|
await self.__inner_start(params)
|
|
|
|
@aiotools.atomic_fg
|
|
async def ensure_restart(self, params: dict) -> None:
|
|
logger = get_logger(0)
|
|
start = bool(self.__proc_task and not self.__stopper_task) # Если запущено и не планирует останавливаться
|
|
await self.ensure_stop(immediately=True)
|
|
if self.__reset_delay > 0:
|
|
logger.info("Waiting %.2f seconds for reset delay ...", self.__reset_delay)
|
|
await asyncio.sleep(self.__reset_delay)
|
|
if start:
|
|
await self.ensure_start(params)
|
|
|
|
@aiotools.atomic_fg
|
|
async def ensure_stop(self, immediately: bool) -> None:
|
|
if self.__proc_task:
|
|
logger = get_logger(0)
|
|
|
|
if immediately:
|
|
if self.__stopper_task:
|
|
if not self.__stopper_wip:
|
|
self.__stopper_task.cancel()
|
|
await asyncio.gather(self.__stopper_task, return_exceptions=True)
|
|
logger.info("Stopping streamer immediately ...")
|
|
await self.__inner_stop()
|
|
else:
|
|
await asyncio.gather(self.__stopper_task, return_exceptions=True)
|
|
else:
|
|
logger.info("Stopping streamer immediately ...")
|
|
await self.__inner_stop()
|
|
|
|
elif not self.__stopper_task:
|
|
|
|
async def delayed_stop() -> None:
|
|
try:
|
|
await asyncio.sleep(self.__shutdown_delay)
|
|
self.__stopper_wip = True
|
|
logger.info("Stopping streamer after delay ...")
|
|
await self.__inner_stop()
|
|
finally:
|
|
self.__stopper_task = None
|
|
self.__stopper_wip = False
|
|
|
|
logger.info("Planning to stop streamer in %.2f seconds ...", self.__shutdown_delay)
|
|
self.__stopper_task = asyncio.create_task(delayed_stop())
|
|
|
|
def is_running(self) -> bool:
|
|
return bool(self.__proc_task)
|
|
|
|
# =====
|
|
|
|
@aiotools.atomic_fg
|
|
async def __inner_start(self, params: dict) -> None:
|
|
assert not self.__proc_task
|
|
self.__proc_params = params
|
|
await self.__run_hook("PRE-START-CMD", self.__pre_start_cmd)
|
|
self.__proc_task = asyncio.create_task(self.__process_task_loop())
|
|
|
|
@aiotools.atomic_fg
|
|
async def __inner_stop(self) -> None:
|
|
assert self.__proc_task
|
|
self.__proc_task.cancel()
|
|
await asyncio.gather(self.__proc_task, return_exceptions=True)
|
|
await self.__kill_process()
|
|
await self.__run_hook("POST-STOP-CMD", self.__post_stop_cmd)
|
|
self.__proc_task = None
|
|
|
|
# =====
|
|
|
|
async def __process_task_loop(self) -> None: # pylint: disable=too-many-branches
|
|
logger = get_logger(0)
|
|
while True: # pylint: disable=too-many-nested-blocks
|
|
try:
|
|
await self.__start_process()
|
|
assert self.__proc is not None
|
|
await aioproc.log_stdout_infinite(self.__proc, logger)
|
|
raise RuntimeError("Streamer unexpectedly died")
|
|
except asyncio.CancelledError:
|
|
break
|
|
except Exception:
|
|
if self.__proc:
|
|
logger.exception("Unexpected streamer error: pid=%d", self.__proc.pid)
|
|
else:
|
|
logger.exception("Can't start streamer")
|
|
await self.__kill_process()
|
|
await asyncio.sleep(1)
|
|
|
|
def __make_cmd(self, cmd: list[str]) -> list[str]:
|
|
return [part.format(**self.__proc_params) for part in cmd]
|
|
|
|
async def __run_hook(self, name: str, cmd: list[str]) -> None:
|
|
logger = get_logger()
|
|
cmd = self.__make_cmd(cmd)
|
|
logger.info("%s: %s", name, tools.cmdfmt(cmd))
|
|
try:
|
|
await aioproc.log_process(cmd, logger, prefix=name)
|
|
except Exception:
|
|
logger.exception("Can't execute %s hook: %s", name, tools.cmdfmt(cmd))
|
|
|
|
async def __start_process(self) -> None:
|
|
assert self.__proc is None
|
|
cmd = self.__make_cmd(self.__cmd)
|
|
self.__proc = await aioproc.run_process(cmd)
|
|
get_logger(0).info("Started streamer pid=%d: %s", self.__proc.pid, tools.cmdfmt(cmd))
|
|
|
|
async def __kill_process(self) -> None:
|
|
if self.__proc:
|
|
await aioproc.kill_process(self.__proc, 1, get_logger(0))
|
|
self.__proc = None
|