fix: 强制设置有效 locale 修复 vnpy_ctp CTP 登录崩溃
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# vnpy_ctp C++ 扩展需要有效 locale,否则 CTP 登录后可能崩溃
|
from locale_fix import ensure_process_locale
|
||||||
os.environ.setdefault("LANG", "C.UTF-8")
|
|
||||||
os.environ.setdefault("LC_ALL", "C.UTF-8")
|
ensure_process_locale()
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ need_install git git
|
|||||||
# vnpy_ctp 在 Linux 上需本地编译(Meson + pkg-config 查找 python3-dev)
|
# vnpy_ctp 在 Linux 上需本地编译(Meson + pkg-config 查找 python3-dev)
|
||||||
echo "==> 安装 vnpy_ctp 编译依赖..."
|
echo "==> 安装 vnpy_ctp 编译依赖..."
|
||||||
apt-get install -y build-essential python3-dev pkg-config locales
|
apt-get install -y build-essential python3-dev pkg-config locales
|
||||||
locale-gen en_US.UTF-8 2>/dev/null || true
|
locale-gen en_US.UTF-8 C.UTF-8 2>/dev/null || true
|
||||||
update-locale LANG=C.UTF-8 LC_ALL=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
|
if ! command -v pm2 &>/dev/null; then
|
||||||
echo "==> 安装 PM2..."
|
echo "==> 安装 PM2..."
|
||||||
|
|||||||
+1
-1
@@ -346,7 +346,7 @@ pm2 restart qihuo
|
|||||||
|-----------|------|
|
|-----------|------|
|
||||||
| `pip install vnpy_ctp` 编译失败 / `Python dependency not found` | 安装 `build-essential python3-dev pkg-config` 后重试 |
|
| `pip install vnpy_ctp` 编译失败 / `Python dependency not found` | 安装 `build-essential python3-dev pkg-config` 后重试 |
|
||||||
| CTP 连接超时 | 检查前置 IP、端口、SimNow 是否维护、是否在允许连接时段 |
|
| 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)) |
|
| 服务器 `180.168.146.187` 超时 | 换 SimNow 备用前置 `182.254.243.31:30001/30011`(见 [SIMNOW.md](./SIMNOW.md)) |
|
||||||
| 已连接但下单拒单 | 检查合约代码、价格精度、是否有足够保证金 |
|
| 已连接但下单拒单 | 检查合约代码、价格精度、是否有足够保证金 |
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -183,7 +183,7 @@ python scripts/test_simnow.py
|
|||||||
| 报错 **4097** / 握手失败 | `pip install -U vnpy vnpy_ctp`,`.env` 设 `SIMNOW_ENV=实盘` |
|
| 报错 **4097** / 握手失败 | `pip install -U vnpy vnpy_ctp`,`.env` 设 `SIMNOW_ENV=实盘` |
|
||||||
| **不合法的登录** | 投资者代码/密码错,或未在快期改过一次密码 |
|
| **不合法的登录** | 投资者代码/密码错,或未在快期改过一次密码 |
|
||||||
| 快期能登、脚本不能 | 多为网络或前置地址,换 SimNow 官网其他组前置试 |
|
| 快期能登、脚本不能 | 多为网络或前置地址,换 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」
|
### 提示「未安装 vnpy / vnpy_ctp」
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ module.exports = {
|
|||||||
max_memory_restart: "300M",
|
max_memory_restart: "300M",
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: "production",
|
NODE_ENV: "production",
|
||||||
LANG: "C.UTF-8",
|
LANG: "en_US.UTF-8",
|
||||||
LC_ALL: "C.UTF-8",
|
LC_ALL: "en_US.UTF-8",
|
||||||
LC_CTYPE: "C.UTF-8",
|
LC_CTYPE: "en_US.UTF-8",
|
||||||
},
|
},
|
||||||
error_file: "/opt/qihuo/logs/pm2-error.log",
|
error_file: "/opt/qihuo/logs/pm2-error.log",
|
||||||
out_file: "/opt/qihuo/logs/pm2-out.log",
|
out_file: "/opt/qihuo/logs/pm2-out.log",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -6,12 +6,13 @@ import os
|
|||||||
import socket
|
import socket
|
||||||
import sys
|
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__)))
|
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
sys.path.insert(0, BASE)
|
sys.path.insert(0, BASE)
|
||||||
|
|
||||||
|
from locale_fix import ensure_process_locale
|
||||||
|
|
||||||
|
ensure_process_locale()
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(os.path.join(BASE, ".env"))
|
load_dotenv(os.path.join(BASE, ".env"))
|
||||||
@@ -38,6 +39,7 @@ def main() -> int:
|
|||||||
env = os.getenv("SIMNOW_ENV", "实盘")
|
env = os.getenv("SIMNOW_ENV", "实盘")
|
||||||
|
|
||||||
print("=== SimNow 配置 ===")
|
print("=== SimNow 配置 ===")
|
||||||
|
print(f"locale = {ensure_process_locale()}")
|
||||||
print(f"SIMNOW_USER = {user or '(未设置)'}")
|
print(f"SIMNOW_USER = {user or '(未设置)'}")
|
||||||
print(f"SIMNOW_PASSWORD = {'*' * 8 if os.getenv('SIMNOW_PASSWORD') else '(未设置)'}")
|
print(f"SIMNOW_PASSWORD = {'*' * 8 if os.getenv('SIMNOW_PASSWORD') else '(未设置)'}")
|
||||||
print(f"SIMNOW_TD = {td}")
|
print(f"SIMNOW_TD = {td}")
|
||||||
|
|||||||
+5
-3
@@ -7,9 +7,9 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
# vnpy_ctp C++ 在部分 Linux 上缺 locale 会抛 std::runtime_error
|
from locale_fix import ensure_process_locale
|
||||||
os.environ.setdefault("LANG", "C.UTF-8")
|
|
||||||
os.environ.setdefault("LC_ALL", "C.UTF-8")
|
ensure_process_locale()
|
||||||
|
|
||||||
from ctp_symbol import ths_to_vnpy_symbol, to_vnpy_exchange
|
from ctp_symbol import ths_to_vnpy_symbol, to_vnpy_exchange
|
||||||
|
|
||||||
@@ -89,6 +89,7 @@ class CtpBridge:
|
|||||||
self._init_engine()
|
self._init_engine()
|
||||||
|
|
||||||
def _init_engine(self) -> None:
|
def _init_engine(self) -> None:
|
||||||
|
ensure_process_locale()
|
||||||
try:
|
try:
|
||||||
from vnpy.event import EventEngine
|
from vnpy.event import EventEngine
|
||||||
from vnpy.trader.engine import MainEngine
|
from vnpy.trader.engine import MainEngine
|
||||||
@@ -163,6 +164,7 @@ class CtpBridge:
|
|||||||
|
|
||||||
self._ee.register(EVENT_LOG, _on_log)
|
self._ee.register(EVENT_LOG, _on_log)
|
||||||
try:
|
try:
|
||||||
|
ensure_process_locale()
|
||||||
logger.info(
|
logger.info(
|
||||||
"CTP 连接 [%s] user=%s td=%s env=%s",
|
"CTP 连接 [%s] user=%s td=%s env=%s",
|
||||||
mode,
|
mode,
|
||||||
|
|||||||
Reference in New Issue
Block a user