This commit is contained in:
songzhuoyuan
2026-08-11 20:41:31 +08:00
parent 2ce13e5bae
commit befdc32aea
24 changed files with 1311 additions and 530 deletions
+77
View File
@@ -0,0 +1,77 @@
"""
统一的外部请求超时控制工具
- install_default_requests_timeout: 给所有基于 requests 的外部请求
akshare、tushare、pywencai 等)注入默认超时,调用方未指定 timeout 时生效。
- call_with_timeout: 在独立线程中执行阻塞调用,超过时限立即放弃等待,
防止个别环节长时间卡死整个程序。
"""
import os
import threading
# 默认连接超时 / 读取超时(单位:秒),可通过环境变量覆盖
DEFAULT_CONNECT_TIMEOUT = float(os.getenv("HTTP_CONNECT_TIMEOUT", "6"))
DEFAULT_READ_TIMEOUT = float(os.getenv("HTTP_READ_TIMEOUT", "20"))
DEFAULT_CALL_TIMEOUT = float(os.getenv("HTTP_CALL_TIMEOUT", "30"))
_installed = False
_install_lock = threading.Lock()
def install_default_requests_timeout(connect_timeout=None, read_timeout=None):
"""给 requests 库注入默认超时(仅当调用方未显式指定 timeout 时生效)。"""
global _installed
with _install_lock:
if _installed:
return
connect = DEFAULT_CONNECT_TIMEOUT if connect_timeout is None else connect_timeout
read = DEFAULT_READ_TIMEOUT if read_timeout is None else read_timeout
try:
import requests.sessions
original_request = requests.sessions.Session.request
def request_with_timeout(self, method, url, **kwargs):
if kwargs.get("timeout") is None:
kwargs["timeout"] = (connect, read)
return original_request(self, method, url, **kwargs)
requests.sessions.Session.request = request_with_timeout
_installed = True
print(f"✅ 已为外部请求注入默认超时(连接 {connect}s / 读取 {read}s")
except Exception as e:
print(f"⚠️ 注入 requests 默认超时失败: {e}")
def call_with_timeout(func, timeout=None, *args, **kwargs):
"""在线程中执行函数,超过 timeout 秒则放弃等待并抛出 TimeoutError。
注意:超时后线程会继续在后台运行直至结束(守护线程),不会影响主程序。
"""
if timeout is None:
timeout = DEFAULT_CALL_TIMEOUT
if timeout <= 0:
return func(*args, **kwargs)
result_box = {}
def _run():
try:
result_box["ok"] = True
result_box["value"] = func(*args, **kwargs)
except BaseException as e: # noqa: BLE001
result_box["ok"] = False
result_box["error"] = e
worker = threading.Thread(target=_run, daemon=True)
worker.start()
worker.join(timeout)
if worker.is_alive():
func_name = getattr(func, "__name__", "function")
raise TimeoutError(f"调用 {func_name} 超过 {timeout}s 未返回,已放弃等待")
if result_box.get("ok"):
return result_box["value"]
raise result_box.get("error", RuntimeError("未知错误"))