diff --git a/app.py b/app.py index 6c43cb7..b130e1a 100644 --- a/app.py +++ b/app.py @@ -1,8 +1,8 @@ import os -# vnpy_ctp C++ 扩展需要有效 locale,否则 CTP 登录后可能崩溃 -os.environ.setdefault("LANG", "C.UTF-8") -os.environ.setdefault("LC_ALL", "C.UTF-8") +from locale_fix import ensure_process_locale + +ensure_process_locale() import sqlite3 import time diff --git a/deploy.sh b/deploy.sh index fa39e45..1a41d28 100644 --- a/deploy.sh +++ b/deploy.sh @@ -29,8 +29,8 @@ need_install git git # vnpy_ctp 在 Linux 上需本地编译(Meson + pkg-config 查找 python3-dev) echo "==> 安装 vnpy_ctp 编译依赖..." apt-get install -y build-essential python3-dev pkg-config locales -locale-gen en_US.UTF-8 2>/dev/null || true -update-locale LANG=C.UTF-8 LC_ALL=C.UTF-8 2>/dev/null || true +locale-gen en_US.UTF-8 C.UTF-8 2>/dev/null || true +update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 2>/dev/null || true if ! command -v pm2 &>/dev/null; then echo "==> 安装 PM2..." diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 34a61de..d717abe 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -346,7 +346,7 @@ pm2 restart qihuo |-----------|------| | `pip install vnpy_ctp` 编译失败 / `Python dependency not found` | 安装 `build-essential python3-dev pkg-config` 后重试 | | CTP 连接超时 | 检查前置 IP、端口、SimNow 是否维护、是否在允许连接时段 | -| 连接后立即崩溃 `locale::facet::_S_create_c_locale` | 系统 locale 未配置;`export LANG=C.UTF-8 LC_ALL=C.UTF-8` 后 `pm2 restart qihuo`,或 `git pull` 使用最新 `ecosystem.config.cjs` | +| 连接后立即崩溃 `locale::facet::_S_create_c_locale` | 安装 locale:`apt install -y locales && locale-gen en_US.UTF-8`,`git pull` 后 `pm2 restart qihuo --update-env` | | 服务器 `180.168.146.187` 超时 | 换 SimNow 备用前置 `182.254.243.31:30001/30011`(见 [SIMNOW.md](./SIMNOW.md)) | | 已连接但下单拒单 | 检查合约代码、价格精度、是否有足够保证金 | diff --git a/docs/SIMNOW.md b/docs/SIMNOW.md index 1e09944..a097667 100644 --- a/docs/SIMNOW.md +++ b/docs/SIMNOW.md @@ -183,7 +183,7 @@ python scripts/test_simnow.py | 报错 **4097** / 握手失败 | `pip install -U vnpy vnpy_ctp`,`.env` 设 `SIMNOW_ENV=实盘` | | **不合法的登录** | 投资者代码/密码错,或未在快期改过一次密码 | | 快期能登、脚本不能 | 多为网络或前置地址,换 SimNow 官网其他组前置试 | -| 连上后进程崩溃 `locale::facet::_S_create_c_locale` | 执行 `export LANG=C.UTF-8 LC_ALL=C.UTF-8`,`pm2 restart qihuo --update-env` | +| 连上后进程崩溃 `locale::facet::_S_create_c_locale` | `apt install -y locales && locale-gen en_US.UTF-8`,`git pull` 后 `pm2 restart qihuo --update-env` | ### 提示「未安装 vnpy / vnpy_ctp」 diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index e2f23f4..8dfa3c8 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -11,9 +11,9 @@ module.exports = { max_memory_restart: "300M", env: { NODE_ENV: "production", - LANG: "C.UTF-8", - LC_ALL: "C.UTF-8", - LC_CTYPE: "C.UTF-8", + LANG: "en_US.UTF-8", + LC_ALL: "en_US.UTF-8", + LC_CTYPE: "en_US.UTF-8", }, error_file: "/opt/qihuo/logs/pm2-error.log", out_file: "/opt/qihuo/logs/pm2-out.log", diff --git a/locale_fix.py b/locale_fix.py new file mode 100644 index 0000000..20e8290 --- /dev/null +++ b/locale_fix.py @@ -0,0 +1,65 @@ +"""Linux 上 vnpy_ctp 连接 CTP 前须设置有效 locale(否则 C++ 层 abort)。""" +from __future__ import annotations + +import locale +import logging +import os +import subprocess + +logger = logging.getLogger(__name__) + +_LOCALE_DONE = False +_LOCALE_NAME = "" + + +def _list_locale_candidates() -> list[str]: + names: list[str] = [] + for item in ( + "C.UTF-8", + "en_US.UTF-8", + "en_US.utf8", + "POSIX", + "C", + ): + if item not in names: + names.append(item) + try: + out = subprocess.check_output(["locale", "-a"], text=True, stderr=subprocess.DEVNULL) + for line in out.splitlines(): + loc = line.strip() + if not loc: + continue + low = loc.lower() + if "utf" in low and loc not in names: + names.insert(0, loc) + except (OSError, subprocess.SubprocessError): + pass + return names + + +def ensure_process_locale() -> str: + """强制设置进程 locale,覆盖系统里无效的旧值。""" + global _LOCALE_DONE, _LOCALE_NAME + if _LOCALE_DONE: + return _LOCALE_NAME + + last_err: locale.Error | None = None + for name in _list_locale_candidates(): + try: + locale.setlocale(locale.LC_ALL, name) + os.environ["LANG"] = name + os.environ["LC_ALL"] = name + os.environ["LC_CTYPE"] = name + _LOCALE_DONE = True + _LOCALE_NAME = name + logger.info("进程 locale 已设置: %s", name) + return name + except locale.Error as exc: + last_err = exc + continue + + raise RuntimeError( + "未找到可用 locale,vnpy_ctp 会在 CTP 登录后崩溃。" + "请执行: apt install -y locales && locale-gen en_US.UTF-8 && " + "update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8" + ) from last_err diff --git a/scripts/test_simnow.py b/scripts/test_simnow.py index 1acf851..687c697 100644 --- a/scripts/test_simnow.py +++ b/scripts/test_simnow.py @@ -6,12 +6,13 @@ import os import socket import sys -os.environ.setdefault("LANG", "C.UTF-8") -os.environ.setdefault("LC_ALL", "C.UTF-8") - BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, BASE) +from locale_fix import ensure_process_locale + +ensure_process_locale() + from dotenv import load_dotenv load_dotenv(os.path.join(BASE, ".env")) @@ -38,6 +39,7 @@ def main() -> int: env = os.getenv("SIMNOW_ENV", "实盘") print("=== SimNow 配置 ===") + print(f"locale = {ensure_process_locale()}") print(f"SIMNOW_USER = {user or '(未设置)'}") print(f"SIMNOW_PASSWORD = {'*' * 8 if os.getenv('SIMNOW_PASSWORD') else '(未设置)'}") print(f"SIMNOW_TD = {td}") diff --git a/vnpy_bridge.py b/vnpy_bridge.py index 1ff2c6a..4230cf0 100644 --- a/vnpy_bridge.py +++ b/vnpy_bridge.py @@ -7,9 +7,9 @@ import threading import time from typing import Any, Optional -# vnpy_ctp C++ 在部分 Linux 上缺 locale 会抛 std::runtime_error -os.environ.setdefault("LANG", "C.UTF-8") -os.environ.setdefault("LC_ALL", "C.UTF-8") +from locale_fix import ensure_process_locale + +ensure_process_locale() from ctp_symbol import ths_to_vnpy_symbol, to_vnpy_exchange @@ -89,6 +89,7 @@ class CtpBridge: self._init_engine() def _init_engine(self) -> None: + ensure_process_locale() try: from vnpy.event import EventEngine from vnpy.trader.engine import MainEngine @@ -163,6 +164,7 @@ class CtpBridge: self._ee.register(EVENT_LOG, _on_log) try: + ensure_process_locale() logger.info( "CTP 连接 [%s] user=%s td=%s env=%s", mode,