1
0
forked from bot/app

🐛 fix: Channel的接收者过滤器的问题,优化重启部分

This commit is contained in:
远野千束 2024-07-31 02:28:25 +08:00
parent 0fb5b84392
commit ca34f9c2a1
21 changed files with 386 additions and 329 deletions

View File

@ -2,8 +2,6 @@ import asyncio
import multiprocessing import multiprocessing
import time import time
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from liteyuki.plugin import PluginMetadata from liteyuki.plugin import PluginMetadata
from liteyuki import get_bot, chan from liteyuki import get_bot, chan
@ -30,24 +28,14 @@ def _():
common_db.save(temp_data) # 更新数据 common_db.save(temp_data) # 更新数据
@liteyuki.on_before_start print("轻雪实例", liteyuki)
def _(): chan.send(liteyuki, "instance")
print("轻雪启动中") # @liteyuki.on_before_start
# def _():
# print("轻雪启动中")
@liteyuki.on_after_start #
async def _(): #
print("轻雪启动完成") # @liteyuki.on_after_start
chan.send("轻雪启动完成") # async def _():
# print("轻雪启动完成")
# chan.send("轻雪启动完成")
@liteyuki.on_after_nonebot_init
async def _():
print("NoneBot初始化完成")
@chan.on_receive(receiver="main")
async def _(data):
print("收到消息", data)
await asyncio.sleep(5)

View File

@ -1,26 +1,17 @@
import asyncio
import multiprocessing
import time import time
from typing import Any, Coroutine, Optional import asyncio
from typing import Any, Optional
from multiprocessing import freeze_support
import nonebot
import liteyuki from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan)
from liteyuki.plugin.load import load_plugin, load_plugins from liteyuki.comm.channel import Channel
from liteyuki.core import IS_MAIN_PROCESS
from liteyuki.core.manager import ProcessManager
from liteyuki.core.spawn_process import mb_run, nb_run
from liteyuki.log import init_log, logger
from liteyuki.plugin import load_plugins
from liteyuki.utils import run_coroutine from liteyuki.utils import run_coroutine
from liteyuki.log import logger, init_log
from src.utils import (
adapter_manager,
driver_manager,
)
from liteyuki.bot.lifespan import (
Lifespan,
LIFESPAN_FUNC,
)
from liteyuki.core.spawn_process import nb_run, ProcessingManager
__all__ = [ __all__ = [
"LiteyukiBot", "LiteyukiBot",
@ -28,18 +19,20 @@ __all__ = [
] ]
"""是否为主进程""" """是否为主进程"""
IS_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
class LiteyukiBot: class LiteyukiBot:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
global _BOT_INSTANCE global _BOT_INSTANCE
_BOT_INSTANCE = self # 引用 _BOT_INSTANCE = self # 引用
if not IS_MAIN_PROCESS:
self.config: dict[str, Any] = kwargs self.config: dict[str, Any] = kwargs
self.lifespan: Lifespan = Lifespan()
self.init(**self.config) # 初始化 self.init(**self.config) # 初始化
else:
self.lifespan: Lifespan = Lifespan()
self.chan = Channel() # 进程通信通道
self.pm: Optional[ProcessManager] = None # 启动时实例化
print("\033[34m" + r""" print("\033[34m" + r"""
__ ______ ________ ________ __ __ __ __ __ __ ______ __ ______ ________ ________ __ __ __ __ __ __ ______
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ | / | / |/ |/ |/ \ / |/ | / |/ | / |/ |
@ -52,62 +45,23 @@ $$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
""" + "\033[0m") """ + "\033[0m")
def run(self, *args, **kwargs): def run(self):
if IS_MAIN_PROCESS: # load_plugins("liteyuki/plugins") # 加载轻雪插件
self._run_nb_in_spawn_process(*args, **kwargs) self.pm = ProcessManager(bot=self, chan=self.chan)
else:
# 子进程启动 self.pm.add_target("melobot", mb_run, **self.config)
load_plugins("liteyuki/plugins") # 加载轻雪插件 self.pm.start("melobot")
driver_manager.init(config=self.config)
adapter_manager.init(self.config) self.pm.add_target("nonebot", nb_run, **self.config)
adapter_manager.register() self.pm.start("nonebot")
nonebot.load_plugin("src.liteyuki_main")
run_coroutine(self.lifespan.after_start()) # 启动前 run_coroutine(self.lifespan.after_start()) # 启动前
def _run_nb_in_spawn_process(self, *args, **kwargs): def restart(self, name: Optional[str] = None):
"""
在新的进程中运行nonebot.run方法该函数在主进程中被调用
Args:
*args:
**kwargs:
Returns:
"""
if IS_MAIN_PROCESS:
timeout_limit: int = 20
should_exit = False
while not should_exit:
ctx = multiprocessing.get_context("spawn")
event = ctx.Event()
ProcessingManager.event = event
process = ctx.Process(
target=nb_run,
args=(event,) + args,
kwargs=kwargs,
)
process.start() # 启动进程
while not should_exit:
if ProcessingManager.event.wait(1):
logger.info("Receive reboot event")
process.terminate()
process.join(timeout_limit)
if process.is_alive():
logger.warning(
f"Process {process.pid} is still alive after {timeout_limit} seconds, force kill it."
)
process.kill()
break
elif process.is_alive():
liteyuki.chan.send("轻雪进程正常运行", "sub")
continue
else:
should_exit = True
def restart(self):
""" """
停止轻雪 停止轻雪
Args:
name: 进程名称, 默认为None, 所有进程
Returns: Returns:
""" """
@ -116,8 +70,11 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
run_coroutine(self.lifespan.before_restart()) run_coroutine(self.lifespan.before_restart())
logger.debug("Running before_shutdown functions...") logger.debug("Running before_shutdown functions...")
run_coroutine(self.lifespan.before_shutdown()) run_coroutine(self.lifespan.before_shutdown())
if name:
ProcessingManager.restart() self.chan.send(1, name)
else:
for name in self.pm.processes:
self.chan.send(1, name)
def init(self, *args, **kwargs): def init(self, *args, **kwargs):
""" """
@ -127,11 +84,9 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
""" """
self.init_config() self.init_config()
self.init_logger() self.init_logger()
if not IS_MAIN_PROCESS:
nonebot.init(**kwargs)
asyncio.run(self.lifespan.after_nonebot_init())
def init_logger(self): def init_logger(self):
# 修改nonebot的日志配置
init_log(config=self.config) init_log(config=self.config)
def init_config(self): def init_config(self):
@ -225,4 +180,8 @@ def get_bot() -> Optional[LiteyukiBot]:
Returns: Returns:
LiteyukiBot: 当前的轻雪实例 LiteyukiBot: 当前的轻雪实例
""" """
if IS_MAIN_PROCESS:
return _BOT_INSTANCE return _BOT_INSTANCE
else:
# 从多进程上下文中获取
pass

View File

@ -10,12 +10,8 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
本模块定义了一个通用的通道类用于进程间通信 本模块定义了一个通用的通道类用于进程间通信
""" """
import threading from multiprocessing import Pipe
from multiprocessing import Queue from typing import Any, Optional, Callable, Awaitable, List, TypeAlias
from queue import Empty, Full
from typing import Any, Awaitable, Callable, List, Optional, TypeAlias
from nonebot import logger
from liteyuki.utils import is_coroutine_callable, run_coroutine from liteyuki.utils import is_coroutine_callable, run_coroutine
@ -29,85 +25,66 @@ FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC
class Channel: class Channel:
def __init__(self, buffer_size: int = 0): """
self._queue = Queue(buffer_size) 通道类用于进程间通信
有两种接收工作方式但是只能选择一种主动接收和被动接收主动接收使用 `receive` 方法被动接收使用 `on_receive` 装饰器
"""
def __init__(self):
self.parent_conn, self.child_conn = Pipe()
self._closed = False self._closed = False
self._on_receive_funcs: List[ON_RECEIVE_FUNC] = [] self._on_receive_funcs: List[ON_RECEIVE_FUNC] = []
self._on_receive_funcs_with_receiver: dict[str, List[ON_RECEIVE_FUNC]] = {} self._on_receive_funcs_with_receiver: dict[str, List[ON_RECEIVE_FUNC]] = {}
self._receiving_thread = threading.Thread(target=self._start_receiver, daemon=True) def send(self, data: Any, receiver: Optional[str] = None):
self._receiving_thread.start()
def send(
self,
data: Any,
receiver: Optional[str] = None,
block: bool = True,
timeout: Optional[float] = None
):
""" """
发送数据 发送数据
Args: Args:
data: 数据 data: 数据
receiver: 接收者如果为None则广播 receiver: 接收者如果为None则广播
block: 是否阻塞
timeout: 超时时间
Returns:
""" """
print(f"send {data} -> {receiver}")
if self._closed: if self._closed:
raise RuntimeError("Cannot send to a closed channel") raise RuntimeError("Cannot send to a closed channel")
try: self.child_conn.send((data, receiver))
self._queue.put((data, receiver), block, timeout)
except Full:
logger.warning("Channel buffer is full, send operation is blocked")
def receive( def receive(self, receiver: str = None) -> Any:
self,
receiver: str = None,
block: bool = True,
timeout: Optional[float] = None
) -> Any:
""" """
接收数据 接收数据
Args: Args:
receiver: 接收者如果为None则接收任意数据 receiver: 接收者如果为None则接收任意数据
block: 是否阻塞
timeout: 超时时间
Returns:
""" """
if self._closed: if self._closed:
raise RuntimeError("Cannot receive from a closed channel") raise RuntimeError("Cannot receive from a closed channel")
try:
while True: while True:
data, data_receiver = self._queue.get(block, timeout) # 判断receiver是否为None或者receiver是否等于接收者是则接收数据否则不动数据
if receiver is None or receiver == data_receiver: if self.parent_conn.poll():
data, receiver = self.parent_conn.recv()
self.parent_conn.send((data, receiver))
self._run_on_receive_funcs(data, receiver)
return data
def peek(self) -> Optional[Any]:
"""
查看管道中的数据不移除
Returns:
"""
if self._closed:
raise RuntimeError("Cannot peek from a closed channel")
if self.parent_conn.poll():
data, receiver = self.parent_conn.recv()
self.parent_conn.send((data, receiver))
return data return data
except Empty:
if not block:
return None return None
raise
def close(self): def close(self):
""" """
关闭通道 关闭通道
Returns:
""" """
self._closed = True self._closed = True
self._queue.close() self.parent_conn.close()
while not self._queue.empty(): self.child_conn.close()
self._queue.get()
def on_receive( def on_receive(self, filter_func: Optional[FILTER_FUNC] = None, receiver: Optional[str] = None) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
self,
filter_func: Optional[FILTER_FUNC] = None,
receiver: Optional[str] = None,
) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
""" """
接收数据并执行函数 接收数据并执行函数
Args: Args:
@ -138,22 +115,11 @@ class Channel:
return decorator return decorator
def _start_receiver(self):
"""
使用多线程启动接收循环在通道实例化时自动启动
Returns:
"""
while True:
data, receiver = self._queue.get(block=True, timeout=None)
self._run_on_receive_funcs(data, receiver)
def _run_on_receive_funcs(self, data: Any, receiver: Optional[str] = None): def _run_on_receive_funcs(self, data: Any, receiver: Optional[str] = None):
""" """
运行接收函数 运行接收函数
Args: Args:
data: 数据 data: 数据
Returns:
""" """
if receiver is None: if receiver is None:
for func in self._on_receive_funcs: for func in self._on_receive_funcs:
@ -165,8 +131,8 @@ class Channel:
def __iter__(self): def __iter__(self):
return self return self
def __next__(self, timeout: Optional[float] = None) -> Any: def __next__(self) -> Any:
return self.receive(block=True, timeout=timeout) return self.receive()
"""默认通道实例,可直接从模块导入使用""" """默认通道实例,可直接从模块导入使用"""

View File

@ -1,3 +1,11 @@
import multiprocessing
from .spawn_process import * from .spawn_process import *
from .manager import *
__all__ = [
"IS_MAIN_PROCESS"
]
IS_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"

93
liteyuki/core/manager.py Normal file
View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
@Time : 2024/7/27 上午11:12
@Author : snowykami
@Email : snowykami@outlook.com
@File : manager.py
@Software: PyCharm
"""
import threading
from multiprocessing import Process
from liteyuki.comm import Channel
from liteyuki.log import logger
TIMEOUT = 10
__all__ = [
"ProcessManager"
]
class ProcessManager:
"""
在主进程中被调用
"""
def __init__(self, bot, chan: Channel):
self.bot = bot
self.chan = chan
self.processes: dict[str, tuple[callable, tuple, dict]] = {}
def start(self, name: str, delay: int = 0):
"""
开启后自动监控进程
Args:
name:
delay:
Returns:
"""
if name not in self.processes:
raise KeyError(f"Process {name} not found.")
def _start():
should_exit = False
while not should_exit:
process = Process(target=self.processes[name][0], args=(self.chan, *self.processes[name][1]), kwargs=self.processes[name][2])
process.start()
while not should_exit:
# 0退出 1重启
data = self.chan.receive(name)
print("Received data: ", data)
if data == 1:
logger.info("Restarting LiteyukiBot...")
process.terminate()
process.join(TIMEOUT)
if process.is_alive():
process.kill()
break
elif data == 0:
logger.info("Stopping LiteyukiBot...")
should_exit = True
process.terminate()
process.join(TIMEOUT)
if process.is_alive():
process.kill()
else:
logger.warning("Unknown data received, ignored.")
if delay:
threading.Timer(delay, _start).start()
else:
threading.Thread(target=_start).start()
def add_target(self, name: str, target, *args, **kwargs):
self.processes[name] = (target, args, kwargs)
def join(self):
for name, process in self.processes:
process.join()
def terminate(self):
for name, process in self.processes:
process.terminate()
process.join(TIMEOUT)
if process.is_alive():
process.kill()
self.processes = []

View File

@ -1,37 +1,50 @@
import threading import threading
from multiprocessing import get_context, Event from multiprocessing import Event, Queue
from typing import Optional
import nonebot import nonebot
from nonebot import logger
from liteyuki.plugin.load import load_plugins import liteyuki
from liteyuki.core.nb import adapter_manager, driver_manager
timeout_limit: int = 20 timeout_limit: int = 20
__all__ = [
"ProcessingManager", """导出对象,用于进程通信"""
"nb_run", chan_in_spawn: Optional["liteyuki.Channel"] = None
]
class ProcessingManager: def nb_run(chan, *args, **kwargs):
event: Event = None
@classmethod
def restart(cls, delay: int = 0):
""" """
发送终止信号 初始化NoneBot并运行在子进程
Args: Args:
delay: 延迟时间默认为0单位秒
*args:
**kwargs:
Returns: Returns:
""" """
if cls.event is None: global chan_in_spawn
raise RuntimeError("ProcessingManager has not been initialized.") chan_in_spawn = chan
if delay > 0: nonebot.init(**kwargs)
threading.Timer(delay, function=cls.event.set).start() driver_manager.init(config=kwargs)
return adapter_manager.init(kwargs)
cls.event.set() adapter_manager.register()
nonebot.load_plugin("src.liteyuki_main")
nonebot.run()
def nb_run(event, *args, **kwargs): def mb_run(chan, *args, **kwargs):
ProcessingManager.event = event """
nonebot.run(*args, **kwargs) 初始化MeloBot并运行在子进程
Args:
chan
*args:
**kwargs:
Returns:
"""
# bot = MeloBot(__name__)
# bot.init(AbstractConnector(cd_time=0))
# bot.run()

View File

@ -1,6 +1,6 @@
from liteyuki import LiteyukiBot from liteyuki import LiteyukiBot
from src.utils import load_from_yaml from src.utils import load_from_yaml
if __name__ in ("__main__", "__mp_main__"): if __name__ == "__main__":
bot = LiteyukiBot(**load_from_yaml("config.yml")) bot = LiteyukiBot(**load_from_yaml("config.yml"))
bot.run() bot.run()

View File

@ -3,6 +3,7 @@ aiofiles~=23.2.1
colored~=2.2.4 colored~=2.2.4
GitPython~=3.1.42 GitPython~=3.1.42
httpx~=0.27.0 httpx~=0.27.0
melobot~=2.6.5
nb-cli~=1.4.1 nb-cli~=1.4.1
nonebot2[fastapi,httpx,websockets]~=2.3.0 nonebot2[fastapi,httpx,websockets]~=2.3.0
nonebot-plugin-htmlrender~=0.3.1 nonebot-plugin-htmlrender~=0.3.1

View File

@ -5,12 +5,13 @@ from typing import Any, AnyStr
import nonebot import nonebot
import pip import pip
from nonebot import Bot, get_driver, require from nonebot import Bot, get_driver, require
from nonebot.adapters import satori from nonebot.adapters import onebot, satori
from nonebot.adapters.onebot.v11 import Message, escape, unescape from nonebot.adapters.onebot.v11 import Message, escape, unescape
from nonebot.exception import MockApiException from nonebot.exception import MockApiException
from nonebot.internal.matcher import Matcher from nonebot.internal.matcher import Matcher
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from liteyuki import Channel
from src.utils.base.config import get_config, load_from_yaml from src.utils.base.config import get_config, load_from_yaml
from src.utils.base.data_manager import StoredConfig, TempConfig, common_db from src.utils.base.data_manager import StoredConfig, TempConfig, common_db
from src.utils.base.language import get_user_lang from src.utils.base.language import get_user_lang
@ -18,9 +19,11 @@ from src.utils.base.ly_typing import T_Bot, T_MessageEvent
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
# from src.liteyuki.core import Reloader # from src.liteyuki.core import Reloader
from src.utils import event as event_utils, satori_utils from src.utils import event as event_utils, satori_utils
from liteyuki.core import ProcessingManager from liteyuki.core.spawn_process import chan_in_spawn
from .api import update_liteyuki from .api import update_liteyuki
from liteyuki.bot import get_bot from liteyuki.bot import get_bot
from ..utils.base import reload
from ..utils.base.ly_function import get_function from ..utils.base.ly_function import get_function
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
@ -78,6 +81,7 @@ async def _(bot: T_Bot, event: T_MessageEvent):
).handle() ).handle()
# Satori OK # Satori OK
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent): async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
global channel_in_spawn_process
await matcher.send("Liteyuki reloading") await matcher.send("Liteyuki reloading")
temp_data = common_db.where_one(TempConfig(), default=TempConfig()) temp_data = common_db.where_one(TempConfig(), default=TempConfig())
@ -94,9 +98,7 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
) )
common_db.save(temp_data) common_db.save(temp_data)
# Reloader.reload(0) reload()
bot = get_bot()
bot.restart()
@on_alconna( @on_alconna(
@ -322,20 +324,18 @@ async def test_for_md_image(bot: T_Bot, api: str, data: dict):
@driver.on_startup @driver.on_startup
async def on_startup(): async def on_startup():
# temp_data = common_db.where_one(TempConfig(), default=TempConfig()) temp_data = common_db.where_one(TempConfig(), default=TempConfig())
# # 储存重启信息 # 储存重启信息
# if temp_data.data.get("reload", False): if temp_data.data.get("reload", False):
# delta_time = time.time() - temp_data.data.get("reload_time", 0) delta_time = time.time() - temp_data.data.get("reload_time", 0)
# temp_data.data["delta_time"] = delta_time temp_data.data["delta_time"] = delta_time
# common_db.save(temp_data) # 更新数据 common_db.save(temp_data) # 更新数据
""" """
该部分迁移至轻雪生命周期 该部分迁移至轻雪生命周期
Returns: Returns:
""" """
pass
@driver.on_shutdown @driver.on_shutdown
async def on_shutdown(): async def on_shutdown():
@ -357,20 +357,30 @@ async def _(bot: T_Bot):
reload_session_id = temp_data.data.get("reload_session_id", 0) reload_session_id = temp_data.data.get("reload_session_id", 0)
delta_time = temp_data.data.get("delta_time", 0) delta_time = temp_data.data.get("delta_time", 0)
common_db.save(temp_data) # 更新数据 common_db.save(temp_data) # 更新数据
if delta_time <= 20.0: # 启动时间太长就别发了,丢人
if isinstance(bot, satori.Bot): if isinstance(bot, satori.Bot):
await bot.send_message( await bot.send_message(
channel_id=reload_session_id, channel_id=reload_session_id,
message="Liteyuki reloaded in %.2f s" % delta_time message="Liteyuki reloaded in %.2f s" % delta_time
) )
else: elif isinstance(bot, onebot.v11.Bot):
await bot.call_api( await bot.send_msg(
"send_msg",
message_type=reload_session_type, message_type=reload_session_type,
user_id=reload_session_id, user_id=reload_session_id,
group_id=reload_session_id, group_id=reload_session_id,
message="Liteyuki reloaded in %.2f s" % delta_time message="Liteyuki reloaded in %.2f s" % delta_time
) )
elif isinstance(bot, onebot.v12.Bot):
await bot.send_message(
message_type=reload_session_type,
user_id=reload_session_id,
group_id=reload_session_id,
message="Liteyuki reloaded in %.2f s" % delta_time,
detail_type="group"
)
# 每天4点更新 # 每天4点更新
@scheduler.scheduled_job("cron", hour=4) @scheduler.scheduled_job("cron", hour=4)
@ -381,7 +391,7 @@ async def every_day_update():
if result: if result:
await broadcast_to_superusers(f"Liteyuki updated: ```\n{logs}\n```") await broadcast_to_superusers(f"Liteyuki updated: ```\n{logs}\n```")
nonebot.logger.info(f"Liteyuki updated: {logs}") nonebot.logger.info(f"Liteyuki updated: {logs}")
ProcessingManager.restart() reload()
else: else:
nonebot.logger.info(logs) nonebot.logger.info(logs)

View File

@ -3,8 +3,8 @@ from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from liteyuki.bot import get_bot from liteyuki.bot import get_bot
from src.utils.base import reload
from src.utils.base.config import get_config from src.utils.base.config import get_config
from liteyuki.core import ProcessingManager
from src.utils.base.resource import load_resources from src.utils.base.resource import load_resources
if get_config("debug", False): if get_config("debug", False):
@ -38,7 +38,7 @@ if get_config("debug", False):
src_excludes_extensions) or event.is_directory or "__pycache__" in event.src_path: src_excludes_extensions) or event.is_directory or "__pycache__" in event.src_path:
return return
nonebot.logger.info(f"{event.src_path} modified, reloading bot...") nonebot.logger.info(f"{event.src_path} modified, reloading bot...")
liteyuki_bot.restart() reload()
class ResourceModifiedHandler(FileSystemEventHandler): class ResourceModifiedHandler(FileSystemEventHandler):

View File

@ -0,0 +1,19 @@
import threading
from nonebot import logger
from liteyuki.core.spawn_process import chan_in_spawn
def reload(delay: float = 0.0, receiver: str = "nonebot"):
"""
重载LiteyukiBot(nonebot)
Args:
receiver: 指定重载的进程
delay:
Returns:
"""
chan_in_spawn.send(1, receiver)
logger.info(f"Reloading LiteyukiBot({receiver})...")