ErisPulse 2.7.1.dev1__tar.gz → 2.7.1.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/PKG-INFO +1 -1
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/pyproject.toml +1 -1
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/run.py +12 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/en.py +1 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/ja.py +1 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/ru.py +1 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +1 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +1 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/constants.py +0 -7
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/en.py +0 -2
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/ja.py +0 -2
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/ru.py +0 -2
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/zh_cn.py +0 -2
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/zh_tw.py +0 -2
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/router.py +22 -57
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/sdk.py +9 -11
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/.gitignore +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/LICENSE +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/README.pypi.md +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/base.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/cli.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/create.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/doctor.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/init.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/install.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/language.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/list.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/self_update.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/types.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/console.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/constants.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/hints.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/registry.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/display.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/file_watcher.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/CLI/utils/scaffold_text.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/adapter.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/client.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/config_schema.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/converter.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/errors.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/manager.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/module.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/router.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/storage.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Bases/websocket.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/base.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/command.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/message.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/message_builder.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/meta.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/notice.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/request.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/session_type.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/Event/wrapper.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/adapter.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/error.css +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/error.html +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/root.css +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/assets/root.html +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/client.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/config.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/constants.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/lifecycle.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/logger.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/master.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/module.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/Core/storage.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/__main__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/adapter.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/bases/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/bases/finder.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/finders/module.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/adapter.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/bases/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/bases/loader.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/module.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/strategy.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/loaders/strict.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/py.typed +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/__init__.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/config_schema.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/context.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/diagnostics.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/exceptions.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/frame_config.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/hints.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/memory.py +0 -0
- {erispulse-2.7.1.dev1 → erispulse-2.7.1.dev2}/src/ErisPulse/runtime/tasks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse
|
|
3
|
-
Version: 2.7.1.
|
|
3
|
+
Version: 2.7.1.dev2
|
|
4
4
|
Summary: Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署
|
|
5
5
|
Project-URL: Homepage, https://github.com/ErisPulse/ErisPulse
|
|
6
6
|
Project-URL: Documentation, https://www.erisdev.com#docs
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ErisPulse"
|
|
7
|
-
version = "2.7.1-dev.
|
|
7
|
+
version = "2.7.1-dev.2"
|
|
8
8
|
description = "Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署"
|
|
9
9
|
readme = "README.pypi.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -183,6 +183,7 @@ class RunCommand(Command):
|
|
|
183
183
|
]
|
|
184
184
|
|
|
185
185
|
crash_count = 0
|
|
186
|
+
process = None
|
|
186
187
|
try:
|
|
187
188
|
while True:
|
|
188
189
|
process = subprocess.Popen(cmd)
|
|
@@ -208,6 +209,17 @@ class RunCommand(Command):
|
|
|
208
209
|
# 继续循环,重新启动子进程
|
|
209
210
|
except KeyboardInterrupt:
|
|
210
211
|
pass
|
|
212
|
+
finally:
|
|
213
|
+
# 运行器退出(Ctrl+C 等)前必须终止子进程,否则孤儿进程会继续持有
|
|
214
|
+
# 端口等资源,导致下次启动时端口被占用(如监听 8000 的残留进程)。
|
|
215
|
+
if process is not None and process.poll() is None:
|
|
216
|
+
console.print(f"[info]{i18n.t('cli.run.terminating_child')}[/]")
|
|
217
|
+
process.terminate()
|
|
218
|
+
try:
|
|
219
|
+
process.wait(timeout=5)
|
|
220
|
+
except subprocess.TimeoutExpired:
|
|
221
|
+
process.kill()
|
|
222
|
+
process.wait()
|
|
211
223
|
|
|
212
224
|
def _run_script(self, script_path: str, reload_mode: bool):
|
|
213
225
|
"""
|
|
@@ -277,6 +277,7 @@ TRANSLATIONS = {
|
|
|
277
277
|
"cli.run.process_exited": "Process exited normally, waiting for file changes to restart",
|
|
278
278
|
"cli.run.process_crashed": "Process crashed (exit code {code}), fix and save to restart",
|
|
279
279
|
"cli.run.subprocess_crashed_retry": "Subprocess crashed, will auto-retry in {seconds}s...",
|
|
280
|
+
"cli.run.terminating_child": "Terminating the bot subprocess to release ports and resources...",
|
|
280
281
|
# ==================== uninstall command ====================
|
|
281
282
|
"cli.uninstall.description": "Uninstall module/adapter packages",
|
|
282
283
|
"cli.uninstall.package_help": "Package name(s) to uninstall (multiple allowed)",
|
|
@@ -277,6 +277,7 @@ TRANSLATIONS = {
|
|
|
277
277
|
"cli.run.process_exited": "プロセスが正常に終了しました。ファイルの変更を待機して再起動",
|
|
278
278
|
"cli.run.process_crashed": "プロセスが異常終了しました(終了コード {code})。修正して保存すると自動再起動",
|
|
279
279
|
"cli.run.subprocess_crashed_retry": "サブプロセスが異常終了しました。{seconds} 秒後に自動再試行します...",
|
|
280
|
+
"cli.run.terminating_child": "ポートなどのリソースを解放するため、ボットの子プロセスを終了しています...",
|
|
280
281
|
# ==================== uninstall コマンド ====================
|
|
281
282
|
"cli.uninstall.description": "モジュール/アダプターパッケージをアンインストール",
|
|
282
283
|
"cli.uninstall.package_help": "アンインストールするパッケージ名(複数指定可)",
|
|
@@ -277,6 +277,7 @@ TRANSLATIONS = {
|
|
|
277
277
|
"cli.run.process_exited": "Процесс завершен нормально, ожидание изменений файлов для перезапуска",
|
|
278
278
|
"cli.run.process_crashed": "Процесс аварийно завершен (код {code}), исправьте и сохраните для перезапуска",
|
|
279
279
|
"cli.run.subprocess_crashed_retry": "Подпроцесс аварийно завершен, автоматическая перезагрузка через {seconds}с...",
|
|
280
|
+
"cli.run.terminating_child": "Завершение дочернего процесса бота для освобождения портов и ресурсов...",
|
|
280
281
|
# ==================== команда uninstall ====================
|
|
281
282
|
"cli.uninstall.description": "Удалить пакеты модулей/адаптеров",
|
|
282
283
|
"cli.uninstall.package_help": "Имя(ена) пакета(ов) для удаления (можно указать несколько)",
|
|
@@ -277,6 +277,7 @@ TRANSLATIONS = {
|
|
|
277
277
|
"cli.run.process_exited": "进程已正常退出,等待文件变更后重启",
|
|
278
278
|
"cli.run.process_crashed": "进程异常退出(退出码 {code}),修复后保存文件将自动重启",
|
|
279
279
|
"cli.run.subprocess_crashed_retry": "子进程异常终止,将在 {seconds} 秒后自动重试...",
|
|
280
|
+
"cli.run.terminating_child": "正在终止机器人子进程,释放端口等资源...",
|
|
280
281
|
# ==================== uninstall 命令 ====================
|
|
281
282
|
"cli.uninstall.description": "卸载模块/适配器包",
|
|
282
283
|
"cli.uninstall.package_help": "要卸载的包名(可指定多个)",
|
|
@@ -277,6 +277,7 @@ TRANSLATIONS = {
|
|
|
277
277
|
"cli.run.process_exited": "程式已正常退出,等待檔案變更後重啟",
|
|
278
278
|
"cli.run.process_crashed": "程式異常退出(退出碼 {code}),修復後儲存檔案將自動重啟",
|
|
279
279
|
"cli.run.subprocess_crashed_retry": "子程序異常終止,將在 {seconds} 秒後自動重試...",
|
|
280
|
+
"cli.run.terminating_child": "正在終止機器人子程序,釋放連接埠等資源...",
|
|
280
281
|
# ==================== uninstall 命令 ====================
|
|
281
282
|
"cli.uninstall.description": "解除安裝模組/適配器包",
|
|
282
283
|
"cli.uninstall.package_help": "要解除安裝的包名(可指定多個)",
|
|
@@ -53,12 +53,6 @@ DEFAULT_SERVER_AUTO_START: Final[bool] = True
|
|
|
53
53
|
# 修改影响: Ctrl+C 后等待 uvicorn 关闭的耐心时间。超时后强制终止。
|
|
54
54
|
SERVER_SHUTDOWN_TIMEOUT_SECS: Final[float] = 5.0
|
|
55
55
|
|
|
56
|
-
# 路由服务器启动时端口被占用后的自动顺延尝试次数(含起始端口)。
|
|
57
|
-
# 运行时行为。启动时若端口被占用,自动依次尝试后续端口,
|
|
58
|
-
# 直至找到可用端口或达到该上限。实际监听端口通过 router.base_url 暴露。
|
|
59
|
-
# 修改影响: 设大允许更多顺延,设小更快放弃;设为 1 表示不顺延,占用即报错。
|
|
60
|
-
DEFAULT_SERVER_PORT_RETRY_LIMIT: Final[int] = 50
|
|
61
|
-
|
|
62
56
|
# ==============================================================================
|
|
63
57
|
# 配置键模板
|
|
64
58
|
#
|
|
@@ -772,7 +766,6 @@ __all__ = [
|
|
|
772
766
|
"DEFAULT_SERVER_AUTO_START",
|
|
773
767
|
"DEFAULT_SERVER_HOST",
|
|
774
768
|
"DEFAULT_SERVER_PORT",
|
|
775
|
-
"DEFAULT_SERVER_PORT_RETRY_LIMIT",
|
|
776
769
|
"DEFAULT_STRICT_MODE",
|
|
777
770
|
"DEFAULT_UNINIT_TIMEOUT_SECS",
|
|
778
771
|
"DEFAULT_USE_GLOBAL_DB",
|
|
@@ -257,8 +257,6 @@ TRANSLATIONS = {
|
|
|
257
257
|
"core.router.start_failed_msg": "Router server startup failed",
|
|
258
258
|
"core.router.start_failed": "Failed to start server: {error}",
|
|
259
259
|
"core.router.port_in_use": "Port {host}:{port} is already in use; the router server could not start. Check whether another ErisPulse instance or another program is using this port (run `netstat -ano` to find the process holding it)",
|
|
260
|
-
"core.router.port_advance": "Port {host}:{requested} is already in use; automatically advanced to {host}:{port}",
|
|
261
|
-
"core.router.port_range_in_use": "Port {host}:{port} and the next {count} ports are all in use; the router server could not start. Check whether multiple ErisPulse instances or other programs are occupying these ports",
|
|
262
260
|
"core.router.stopping": "Stopping router server...",
|
|
263
261
|
"core.router.stopped_normal": "Router server stopped normally",
|
|
264
262
|
"core.router.stopped_cancelled": "Router server was cancelled",
|
|
@@ -257,8 +257,6 @@ TRANSLATIONS = {
|
|
|
257
257
|
"core.router.start_failed_msg": "ルーターサーバーの起動に失敗しました",
|
|
258
258
|
"core.router.start_failed": "サーバーの起動に失敗しました: {error}",
|
|
259
259
|
"core.router.port_in_use": "ポート {host}:{port} は既に使用されています。ルーターサーバーを起動できません。別の ErisPulse インスタンスや他のプログラムがこのポートを使用していないか確認してください(`netstat -ano` で使用中のプロセスを確認できます)",
|
|
260
|
-
"core.router.port_advance": "ポート {host}:{requested} は既に使用されています。{host}:{port} に自動で切り替えました",
|
|
261
|
-
"core.router.port_range_in_use": "ポート {host}:{port} とその後の {count} 個のポートがすべて使用中です。ルーターサーバーを起動できません。複数の ErisPulse インスタンスや他のプログラムがこれらのポートを占有していないか確認してください",
|
|
262
260
|
"core.router.stopping": "ルーターサーバーを停止しています...",
|
|
263
261
|
"core.router.stopped_normal": "ルーターサーバーは正常に停止しました",
|
|
264
262
|
"core.router.stopped_cancelled": "ルーターサーバーはキャンセルされました",
|
|
@@ -257,8 +257,6 @@ TRANSLATIONS = {
|
|
|
257
257
|
"core.router.start_failed_msg": "Ошибка запуска сервера маршрутизации",
|
|
258
258
|
"core.router.start_failed": "Ошибка запуска сервера: {error}",
|
|
259
259
|
"core.router.port_in_use": "Порт {host}:{port} уже занят, сервер маршрутизации не может запуститься. Проверьте, не использует ли этот порт другой экземпляр ErisPulse или другая программа (выполните `netstat -ano`, чтобы найти процесс, удерживающий порт)",
|
|
260
|
-
"core.router.port_advance": "Порт {host}:{requested} уже занят, автоматически переключено на {host}:{port}",
|
|
261
|
-
"core.router.port_range_in_use": "Порт {host}:{port} и последующие {count} портов заняты, сервер маршрутизации не может запуститься. Проверьте, не занимают ли эти порты несколько экземпляров ErisPulse или других программ",
|
|
262
260
|
"core.router.stopping": "Остановка сервера маршрутизации...",
|
|
263
261
|
"core.router.stopped_normal": "Сервер маршрутизации остановлен нормально",
|
|
264
262
|
"core.router.stopped_cancelled": "Работа сервера маршрутизации была отменена",
|
|
@@ -257,8 +257,6 @@ TRANSLATIONS = {
|
|
|
257
257
|
"core.router.start_failed_msg": "路由服务器启动失败",
|
|
258
258
|
"core.router.start_failed": "启动服务器失败: {error}",
|
|
259
259
|
"core.router.port_in_use": "端口 {host}:{port} 已被占用,路由服务器无法启动。请检查是否有另一个 ErisPulse 实例或其他程序正在使用该端口(可执行 `netstat -ano` 查看占用进程)",
|
|
260
|
-
"core.router.port_advance": "端口 {host}:{requested} 已被占用,已自动顺延至 {host}:{port}",
|
|
261
|
-
"core.router.port_range_in_use": "端口 {host}:{port} 及其后 {count} 个端口均已被占用,无法启动路由服务器。请检查是否有多个 ErisPulse 实例或其他程序占用了这些端口",
|
|
262
260
|
"core.router.stopping": "正在停止路由服务器...",
|
|
263
261
|
"core.router.stopped_normal": "路由服务器已正常停止",
|
|
264
262
|
"core.router.stopped_cancelled": "路由服务器已被取消",
|
|
@@ -257,8 +257,6 @@ TRANSLATIONS = {
|
|
|
257
257
|
"core.router.start_failed_msg": "路由伺服器啟動失敗",
|
|
258
258
|
"core.router.start_failed": "啟動伺服器失敗: {error}",
|
|
259
259
|
"core.router.port_in_use": "連接埠 {host}:{port} 已被占用,路由伺服器無法啟動。請檢查是否有另一個 ErisPulse 實例或其他程式正在使用該連接埠(可執行 `netstat -ano` 查看占用程序)",
|
|
260
|
-
"core.router.port_advance": "連接埠 {host}:{requested} 已被占用,已自動順延至 {host}:{port}",
|
|
261
|
-
"core.router.port_range_in_use": "連接埠 {host}:{port} 及其後 {count} 個連接埠均已被占用,無法啟動路由伺服器。請檢查是否有多個 ErisPulse 實例或其他程式占用了這些連接埠",
|
|
262
260
|
"core.router.stopping": "正在停止路由伺服器...",
|
|
263
261
|
"core.router.stopped_normal": "路由伺服器已正常停止",
|
|
264
262
|
"core.router.stopped_cancelled": "路由伺服器已被取消",
|
|
@@ -53,7 +53,6 @@ from .constants import (
|
|
|
53
53
|
DEFAULT_SECURITY_HEADERS,
|
|
54
54
|
DEFAULT_SERVER_HOST,
|
|
55
55
|
DEFAULT_SERVER_PORT,
|
|
56
|
-
DEFAULT_SERVER_PORT_RETRY_LIMIT,
|
|
57
56
|
DEFAULT_WS_AUTO_ACCEPT,
|
|
58
57
|
FALLBACK_IPV4,
|
|
59
58
|
FALLBACK_IPV6_HOST,
|
|
@@ -2601,26 +2600,23 @@ class RouterManager:
|
|
|
2601
2600
|
self._get_local_ips()
|
|
2602
2601
|
self._apply_config()
|
|
2603
2602
|
|
|
2604
|
-
#
|
|
2605
|
-
#
|
|
2606
|
-
#
|
|
2607
|
-
#
|
|
2608
|
-
|
|
2609
|
-
bind_port = self._find_available_port(
|
|
2610
|
-
host, port, DEFAULT_SERVER_PORT_RETRY_LIMIT
|
|
2611
|
-
)
|
|
2603
|
+
# 同步探测端口是否可绑定。uvicorn 在端口被占用时会执行 sys.exit(3),
|
|
2604
|
+
# 其 SystemExit 会被事件循环重新抛出并取消所有任务,导致进程异常退出并
|
|
2605
|
+
# 刷屏大量 "Task was destroyed but it is pending"。此处先同步 bind 探测,
|
|
2606
|
+
# 端口被占用时在启动前抛出清晰、可操作的错误,由上层按致命错误处理。
|
|
2607
|
+
self._check_port_available(host, port)
|
|
2612
2608
|
|
|
2613
2609
|
config = uvicorn.Config(
|
|
2614
2610
|
self.app,
|
|
2615
2611
|
host=host,
|
|
2616
|
-
port=
|
|
2612
|
+
port=port,
|
|
2617
2613
|
log_level="warning",
|
|
2618
2614
|
ssl_certfile=ssl_certfile,
|
|
2619
2615
|
ssl_keyfile=ssl_keyfile,
|
|
2620
2616
|
)
|
|
2621
2617
|
self._uvicorn_server = uvicorn.Server(config)
|
|
2622
2618
|
|
|
2623
|
-
self.base_url = f"http{'s' if ssl_certfile else ''}://{host}:{
|
|
2619
|
+
self.base_url = f"http{'s' if ssl_certfile else ''}://{host}:{port}"
|
|
2624
2620
|
display_url = self._format_display_url(self.base_url)
|
|
2625
2621
|
http_count = sum(len(paths) for paths in self._http_routes.values())
|
|
2626
2622
|
ws_count = sum(len(paths) for paths in self._websocket_routes.values())
|
|
@@ -2662,7 +2658,7 @@ class RouterManager:
|
|
|
2662
2658
|
data={
|
|
2663
2659
|
"base_url": self.base_url,
|
|
2664
2660
|
"host": host,
|
|
2665
|
-
"port":
|
|
2661
|
+
"port": port,
|
|
2666
2662
|
},
|
|
2667
2663
|
)
|
|
2668
2664
|
except Exception as e:
|
|
@@ -2684,18 +2680,21 @@ class RouterManager:
|
|
|
2684
2680
|
logger.error(i18n.t("core.router.start_failed", error=e))
|
|
2685
2681
|
raise e
|
|
2686
2682
|
|
|
2687
|
-
def
|
|
2683
|
+
def _check_port_available(self, host: str, port: int) -> None:
|
|
2688
2684
|
"""
|
|
2689
|
-
|
|
2685
|
+
同步探测端口是否可绑定,避免 uvicorn 异步启动失败后引发级联错误
|
|
2690
2686
|
|
|
2691
2687
|
uvicorn 在端口被占用时会执行 ``sys.exit(STARTUP_FAILURE)``,其 SystemExit
|
|
2692
2688
|
会被事件循环重新抛出并取消所有任务,导致进程异常退出并刷屏大量
|
|
2693
|
-
"Task was destroyed but it is pending"。此处先同步 bind
|
|
2694
|
-
|
|
2689
|
+
"Task was destroyed but it is pending"。此处先同步 bind 探测端口,若被占用
|
|
2690
|
+
则在启动前抛出清晰的 i18n 提示,由上层按致命错误处理(不自动顺延端口,
|
|
2691
|
+
避免生产环境暴露的端口变化导致外部访问失败)。
|
|
2695
2692
|
|
|
2696
2693
|
:param host: str 监听地址
|
|
2697
2694
|
:param port: int 监听端口
|
|
2698
|
-
|
|
2695
|
+
|
|
2696
|
+
:raises RuntimeError: 当端口被占用时抛出,携带友好的错误提示
|
|
2697
|
+
:raises OSError: 其他绑定错误(如无权限绑定特权端口)
|
|
2699
2698
|
"""
|
|
2700
2699
|
try:
|
|
2701
2700
|
family = socket.AF_INET6 if ":" in host else socket.AF_INET
|
|
@@ -2704,46 +2703,12 @@ class RouterManager:
|
|
|
2704
2703
|
probe.bind((host, port))
|
|
2705
2704
|
finally:
|
|
2706
2705
|
probe.close()
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
) -> int:
|
|
2714
|
-
"""
|
|
2715
|
-
在 [port, port + retry_limit) 范围内寻找第一个可绑定的端口
|
|
2716
|
-
|
|
2717
|
-
端口被占用时自动顺延,实际监听端口通过
|
|
2718
|
-
router.base_url 暴露给下游。若范围内全部被占用,抛出清晰的错误提示。
|
|
2719
|
-
|
|
2720
|
-
:param host: str 监听地址
|
|
2721
|
-
:param port: int 期望端口(顺延起点)
|
|
2722
|
-
:param retry_limit: int 尝试的端口个数(含起点),最小为 1
|
|
2723
|
-
:return: int 实际可绑定的端口
|
|
2724
|
-
|
|
2725
|
-
:raises RuntimeError: 当范围内端口全部被占用时抛出
|
|
2726
|
-
"""
|
|
2727
|
-
for candidate in range(port, port + max(1, retry_limit)):
|
|
2728
|
-
if self._is_port_bindable(host, candidate):
|
|
2729
|
-
if candidate != port:
|
|
2730
|
-
logger.warning(
|
|
2731
|
-
i18n.t(
|
|
2732
|
-
"core.router.port_advance",
|
|
2733
|
-
host=host,
|
|
2734
|
-
requested=port,
|
|
2735
|
-
port=candidate,
|
|
2736
|
-
)
|
|
2737
|
-
)
|
|
2738
|
-
return candidate
|
|
2739
|
-
raise RuntimeError(
|
|
2740
|
-
i18n.t(
|
|
2741
|
-
"core.router.port_range_in_use",
|
|
2742
|
-
host=host,
|
|
2743
|
-
port=port,
|
|
2744
|
-
count=retry_limit,
|
|
2745
|
-
)
|
|
2746
|
-
)
|
|
2706
|
+
except OSError as e:
|
|
2707
|
+
if e.errno == errno.EADDRINUSE:
|
|
2708
|
+
raise RuntimeError(
|
|
2709
|
+
i18n.t("core.router.port_in_use", host=host, port=port)
|
|
2710
|
+
) from e
|
|
2711
|
+
raise
|
|
2747
2712
|
|
|
2748
2713
|
def _start_rate_limit_cleanup(self) -> None:
|
|
2749
2714
|
"""
|
|
@@ -430,17 +430,15 @@ class SDK:
|
|
|
430
430
|
# 跳过 HTTP 服务器启动(适用于纯 WebSocket/轮询适配器)
|
|
431
431
|
self.logger.info(i18n.t("core.sdk.init.router_start_skipped"))
|
|
432
432
|
else:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
i18n.t("core.sdk.init.router_start_failed", error=e)
|
|
443
|
-
)
|
|
433
|
+
# 路由服务器是适配器/模块对外服务的核心基础设施,绑定失败
|
|
434
|
+
# (如端口被占用)属于致命初始化错误:不自动顺延端口(生产环境
|
|
435
|
+
# 暴露的端口变化会导致外部访问失败),直接抛出使 init() 失败退出。
|
|
436
|
+
await self.router.start(
|
|
437
|
+
host=_server_config["host"],
|
|
438
|
+
port=_server_config["port"],
|
|
439
|
+
ssl_certfile=_server_config.get("ssl_certfile"),
|
|
440
|
+
ssl_keyfile=_server_config.get("ssl_keyfile"),
|
|
441
|
+
)
|
|
444
442
|
|
|
445
443
|
# 获取加载耗时
|
|
446
444
|
load_duration = self.lifecycle.stop_timer(LIFECYCLE_TIMER_CORE_INIT)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|