diff --git a/liteyuki/bot/__init__.py b/liteyuki/bot/__init__.py index 118d2981..35904a7b 100644 --- a/liteyuki/bot/__init__.py +++ b/liteyuki/bot/__init__.py @@ -4,10 +4,7 @@ import platform import sys import threading import time -from typing import Any, Iterable, Optional - -from watchdog.events import FileSystemEventHandler -from watchdog.observers import Observer +from typing import Any, Optional from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan) from liteyuki.comm import get_channel @@ -38,6 +35,7 @@ class LiteyukiBot: self.lifespan = Lifespan() self.process_manager: ProcessManager = ProcessManager(bot=self) + self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True) @@ -50,15 +48,9 @@ class LiteyukiBot: """ 启动逻辑 """ - self.loop_thread.start() # 启动事件循环 - asyncio.run(self.lifespan.before_start()) # 启动前钩子 - - asyncio.run(self.lifespan.after_start()) # 启动后钩子 - self.start_watcher() # 启动文件监视器,后续准备插件化 - self.keep_running() - - def start_watcher(self): - pass + self.lifespan.before_start() # 启动前钩子 + self.process_manager.start_all() + self.lifespan.after_start() # 启动后钩子 def restart(self, delay: int = 0): """ @@ -120,6 +112,15 @@ class LiteyukiBot: def init_config(self): pass + def stop(self): + """ + 停止轻雪 + Returns: + + """ + self.stop_event.set() + self.loop.stop() + def on_before_start(self, func: LIFESPAN_FUNC): """ 注册启动前的函数 @@ -198,9 +199,6 @@ class LiteyukiBot: """ return self.lifespan.on_after_nonebot_init(func) - def keep_running(self): - self.stop_event.wait() - _BOT_INSTANCE: Optional[LiteyukiBot] = None diff --git a/liteyuki/bot/lifespan.py b/liteyuki/bot/lifespan.py index c0eda044..a8c427b2 100644 --- a/liteyuki/bot/lifespan.py +++ b/liteyuki/bot/lifespan.py @@ -8,23 +8,27 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved @File : lifespan.py @Software: PyCharm """ +import asyncio from typing import Any, Awaitable, Callable, TypeAlias from liteyuki.log import logger -from liteyuki.utils import is_coroutine_callable +from liteyuki.utils import is_coroutine_callable, async_wrapper SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any] ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]] LIFESPAN_FUNC: TypeAlias = SYNC_LIFESPAN_FUNC | ASYNC_LIFESPAN_FUNC +SYNC_PROCESS_LIFESPAN_FUNC: TypeAlias = Callable[[str], Any] +ASYNC_PROCESS_LIFESPAN_FUNC: TypeAlias = Callable[[str], Awaitable[Any]] +PROCESS_LIFESPAN_FUNC: TypeAlias = SYNC_PROCESS_LIFESPAN_FUNC | ASYNC_PROCESS_LIFESPAN_FUNC + class Lifespan: def __init__(self) -> None: """ 轻雪生命周期管理,启动、停止、重启 """ - - self.life_flag: int = 0 # 0: 启动前,1: 启动后,2: 停止前,3: 停止后 + self.life_flag: int = 0 self._before_start_funcs: list[LIFESPAN_FUNC] = [] self._after_start_funcs: list[LIFESPAN_FUNC] = [] @@ -38,18 +42,26 @@ class Lifespan: self._after_nonebot_init_funcs: list[LIFESPAN_FUNC] = [] @staticmethod - async def _run_funcs(funcs: list[LIFESPAN_FUNC]) -> None: + def _run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None: """ 运行函数 Args: funcs: Returns: """ + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + tasks = [] for func in funcs: if is_coroutine_callable(func): - await func() + tasks.append(func(*args, **kwargs)) else: - func() + tasks.append(async_wrapper(func)(*args, **kwargs)) + loop.run_until_complete(asyncio.gather(*tasks)) def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: """ @@ -131,59 +143,51 @@ class Lifespan: self._after_nonebot_init_funcs.append(func) return func - async def before_start(self) -> None: + def before_start(self) -> None: """ 启动前 Returns: """ logger.debug("Running before_start functions") - await self._run_funcs(self._before_start_funcs) + self._run_funcs(self._before_start_funcs) - async def after_start(self) -> None: + def after_start(self) -> None: """ 启动后 Returns: """ logger.debug("Running after_start functions") - await self._run_funcs(self._after_start_funcs) + self._run_funcs(self._after_start_funcs) - async def before_process_shutdown(self) -> None: + def before_process_shutdown(self) -> None: """ 停止前 Returns: """ logger.debug("Running before_shutdown functions") - await self._run_funcs(self._before_process_shutdown_funcs) + self._run_funcs(self._before_process_shutdown_funcs) - async def after_shutdown(self) -> None: + def after_shutdown(self) -> None: """ 停止后 Returns: """ logger.debug("Running after_shutdown functions") - await self._run_funcs(self._after_shutdown_funcs) + self._run_funcs(self._after_shutdown_funcs) - async def before_process_restart(self) -> None: + def before_process_restart(self) -> None: """ 重启前 Returns: """ logger.debug("Running before_restart functions") - await self._run_funcs(self._before_process_restart_funcs) + self._run_funcs(self._before_process_restart_funcs) - async def after_restart(self) -> None: + def after_restart(self) -> None: """ 重启后 Returns: """ logger.debug("Running after_restart functions") - await self._run_funcs(self._after_restart_funcs) - - async def after_nonebot_init(self) -> None: - """ - NoneBot 初始化后 - Returns: - """ - logger.debug("Running after_nonebot_init functions") - await self._run_funcs(self._after_nonebot_init_funcs) + self._run_funcs(self._after_restart_funcs) diff --git a/liteyuki/core/manager.py b/liteyuki/core/manager.py index fa10b482..b2df2958 100644 --- a/liteyuki/core/manager.py +++ b/liteyuki/core/manager.py @@ -36,53 +36,63 @@ class ProcessManager: self.targets: dict[str, tuple[callable, tuple, dict]] = {} self.processes: dict[str, Process] = {} - def start(self, name: str, delay: int = 0): + def start(self, name: str): """ 开启后自动监控进程,并添加到进程字典中 Args: name: - delay: - Returns: """ if name not in self.targets: raise KeyError(f"Process {name} not found.") - def _start(): - should_exit = False - while not should_exit: - chan_active = get_channel(f"{name}-active") - process = Process(target=self.targets[name][0], args=self.targets[name][1], - kwargs=self.targets[name][2]) - self.processes[name] = process - process.start() - while not should_exit: - # 0退出 1重启 - data = chan_active.receive() - if data == 1: - # 重启 - if self.is_process_alive(name): - logger.info(f"Restarting process {name}") - asyncio.run(self.bot.lifespan.before_process_shutdown()) - asyncio.run(self.bot.lifespan.before_process_restart()) - self.terminate(name) - break - else: - logger.warning(f"Process {name} is not restartable, cannot restart.") + chan_active = get_channel(f"{name}-active") - elif data == 0: + def _start_process(): + process = Process(target=self.targets[name][0], args=self.targets[name][1], + kwargs=self.targets[name][2]) + self.processes[name] = process + + process.start() + + # 启动进程并监听信号 + _start_process() + + def _start_monitor(): + while True: + try: + data = chan_active.receive() + if data == 0: + # 停止 logger.info(f"Stopping process {name}") - asyncio.run(self.bot.lifespan.before_process_shutdown()) - should_exit = True + self.bot.lifespan.before_process_shutdown() self.terminate(name) + break + elif data == 1: + # 重启 + logger.info(f"Restarting process {name}") + self.bot.lifespan.before_process_shutdown() + self.bot.lifespan.before_process_restart() + self.terminate(name) + _start_process() + continue else: logger.warning("Unknown data received, ignored.") + except KeyboardInterrupt: + logger.info(f"Stopping process {name}") + self.bot.lifespan.before_process_shutdown() + self.terminate_all() + break - if delay: - threading.Timer(delay, _start).start() - else: - threading.Thread(target=_start).start() + threading.Thread(target=_start_monitor).start() + + def start_all(self): + """ + 启动所有进程 + """ + for name in self.targets: + self.start(name) def add_target(self, name: str, target, args: tuple = (), kwargs=None): """ @@ -100,7 +110,6 @@ class ProcessManager: kwargs["chan_active"] = chan_active kwargs["chan_passive"] = chan_passive self.targets[name] = (target, args, kwargs) - set_channels( { f"{name}-active" : chan_active, @@ -128,6 +137,7 @@ class ProcessManager: process.join(TIMEOUT) if process.is_alive(): process.kill() + logger.success(f"Process {name} terminated.") def terminate_all(self): for name in self.targets: @@ -144,5 +154,4 @@ class ProcessManager: """ if name not in self.targets: raise logger.warning(f"Process {name} not found.") - process = self.processes[name] - return process.is_alive() + return self.processes[name].is_alive() diff --git a/liteyuki/utils.py b/liteyuki/utils.py index 14cb8ef3..73c4987d 100644 --- a/liteyuki/utils.py +++ b/liteyuki/utils.py @@ -72,3 +72,18 @@ def path_to_module_name(path: Path) -> str: return ".".join(rel_path.parts[:-1]) else: return ".".join(rel_path.parts[:-1] + (rel_path.stem,)) + + +def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]: + """ + 异步包装器 + Args: + func: Sync Callable + Returns: + Coroutine: Asynchronous Callable + """ + + async def wrapper(*args, **kwargs): + return func(*args, **kwargs) + wrapper.__signature__ = inspect.signature(func) + return wrapper diff --git a/src/liteyuki_plugins/lifespan_monitor.py b/src/liteyuki_plugins/lifespan_monitor.py index a42d4d1d..ff8bea5c 100644 --- a/src/liteyuki_plugins/lifespan_monitor.py +++ b/src/liteyuki_plugins/lifespan_monitor.py @@ -21,9 +21,6 @@ __plugin_meta__ = PluginMetadata( bot = get_bot() -# nbp_chan = get_channel("nonebot-passive") -# mbp_chan = get_channel("melobot-passive") - @bot.on_before_start def _(): logger.info("生命周期监控器:准备启动") @@ -40,5 +37,6 @@ def _(): @bot.on_after_start -def _(): +async def _(): + await asyncio.sleep(6) logger.info("生命周期监控器:启动完成") diff --git a/liteyuki/plugins/nonebot_launcher/__init__.py b/src/liteyuki_plugins/nonebot_launcher/__init__.py similarity index 90% rename from liteyuki/plugins/nonebot_launcher/__init__.py rename to src/liteyuki_plugins/nonebot_launcher/__init__.py index 3812a5fc..2be0a808 100644 --- a/liteyuki/plugins/nonebot_launcher/__init__.py +++ b/src/liteyuki_plugins/nonebot_launcher/__init__.py @@ -11,11 +11,9 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved import nonebot -from liteyuki.plugin import PluginMetadata -from liteyuki import get_bot from liteyuki.comm import Channel, set_channel from liteyuki.core import IS_MAIN_PROCESS - +from liteyuki.plugin import PluginMetadata from .nb_utils import adapter_manager, driver_manager __plugin_meta__ = PluginMetadata( @@ -54,7 +52,6 @@ if IS_MAIN_PROCESS: liteyuki = get_bot() - @liteyuki.on_after_start - def start_run_nonebot(): + @liteyuki.on_before_start + async def start_run_nonebot(): liteyuki.process_manager.add_target(name="nonebot", target=nb_run, args=(), kwargs=liteyuki.config) - liteyuki.process_manager.start("nonebot") diff --git a/liteyuki/plugins/nonebot_launcher/dev_reloader.py b/src/liteyuki_plugins/nonebot_launcher/dev_reloader.py similarity index 100% rename from liteyuki/plugins/nonebot_launcher/dev_reloader.py rename to src/liteyuki_plugins/nonebot_launcher/dev_reloader.py diff --git a/liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/__init__.py b/src/liteyuki_plugins/nonebot_launcher/nb_utils/adapter_manager/__init__.py similarity index 100% rename from liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/__init__.py rename to src/liteyuki_plugins/nonebot_launcher/nb_utils/adapter_manager/__init__.py diff --git a/liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/onebot.py b/src/liteyuki_plugins/nonebot_launcher/nb_utils/adapter_manager/onebot.py similarity index 100% rename from liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/onebot.py rename to src/liteyuki_plugins/nonebot_launcher/nb_utils/adapter_manager/onebot.py diff --git a/liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/satori.py b/src/liteyuki_plugins/nonebot_launcher/nb_utils/adapter_manager/satori.py similarity index 100% rename from liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/satori.py rename to src/liteyuki_plugins/nonebot_launcher/nb_utils/adapter_manager/satori.py diff --git a/liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/__init__.py b/src/liteyuki_plugins/nonebot_launcher/nb_utils/driver_manager/__init__.py similarity index 100% rename from liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/__init__.py rename to src/liteyuki_plugins/nonebot_launcher/nb_utils/driver_manager/__init__.py diff --git a/liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/auto_set_env.py b/src/liteyuki_plugins/nonebot_launcher/nb_utils/driver_manager/auto_set_env.py similarity index 100% rename from liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/auto_set_env.py rename to src/liteyuki_plugins/nonebot_launcher/nb_utils/driver_manager/auto_set_env.py diff --git a/liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/defines.py b/src/liteyuki_plugins/nonebot_launcher/nb_utils/driver_manager/defines.py similarity index 100% rename from liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/defines.py rename to src/liteyuki_plugins/nonebot_launcher/nb_utils/driver_manager/defines.py diff --git a/src/nonebot_plugins/liteyuki_crt_utils/__init__.py b/src/nonebot_plugins/liteyuki_crt_utils/__init__.py index 63e21d69..33f1638c 100644 --- a/src/nonebot_plugins/liteyuki_crt_utils/__init__.py +++ b/src/nonebot_plugins/liteyuki_crt_utils/__init__.py @@ -6,6 +6,7 @@ from .rt_guide import * from .crt_matchers import * __plugin_meta__ = PluginMetadata( + name="CRT生成工具", description="一些CRT牌子生成器", usage="我觉得你应该会用",