Skip to content

[Bug] 异步路径中调用同步 requests(无超时),网络异常时冻结整个事件循环 #81

Description

@TBice123123

问题描述

SDK 的异步调用路径中直接调用了基于同步 requests 且未设置 timeout 的方法。当到 api.dingtalk.com 的网络是"挂起"状态(TCP 半开,不报错也不返回)时,requests 会阻塞到 OS 层 TCP 超时(分钟级),导致整个 asyncio 事件循环冻结——进程内所有协程停止调度,且同步代码没有 await 点,task.cancel() 无法生效,进程表现为假死,只能强杀。

具体位置

1. DingTalkStreamClient.start() → open_connection()(dingtalk_stream/stream.py)

# stream.py:65 — async def start() 的事件循环里直接同步调用
connection = self.open_connection()

# stream.py:172 — open_connection() 内部,同步 requests 且无 timeout
response = requests.post(DingTalkStreamClient.OPEN_CONNECTION_API,
                         headers=request_headers,
                         data=request_body)

2. 各 async_* 卡片方法 → get_access_token()(card_replier.py → stream.py)

# card_replier.py — async_create_and_send_card / async_streaming /
# async_put_card_data 等异步方法开头均为:
access_token = self.dingtalk_client.get_access_token()

# stream.py:217 — get_access_token() 内部,同样是无 timeout 的 requests.post
response = requests.post(url, headers=request_headers, data=json.dumps(values))

值得注意的是,卡片接口都有 aiohttp 实现的异步双版本(streaming/async_streaming 等),唯独 open_connection 和 get_access_token 没有异步版本,形成了"异步链路中的同步阻塞点"。

影响

正常情况下无感(get_access_token 有约 2 小时缓存,open_connection 仅在(重)连时调用)。但触发点恰好都是服务最脆弱的时刻:启动建连、断线重连、token 过期刷新。一旦此时网络异常挂起,整个进程(包括与钉钉无关的其它协程)冻结数分钟,重连机制本身也失效。

复现

import asyncio
from dingtalk_stream import Credential, DingTalkStreamClient

async def main():
    client = DingTalkStreamClient(Credential("fake", "fake"))
    task = asyncio.create_task(client.start())
    await asyncio.sleep(1)
    task.cancel()
    # 网络不通时 cancel 无法打断阻塞中的 requests.post,这里超时
    await asyncio.wait_for(task, timeout=5)

asyncio.run(main())

在无法访问 api.dingtalk.com 的环境(或将该域名指向黑洞地址)下运行即可复现。

建议

  1. 为 open_connection / get_access_token 增加异步版本(SDK 已依赖 aiohttp),异步调用点切换过去;或最低成本地在调用点用 await asyncio.to_thread(...) 包装;
  2. 为所有 requests / aiohttp 调用设置显式 timeout。

环境

  • dingtalk-stream 0.24.3
  • Python 3.11,Windows 11(与平台无关,Linux 同样存在)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions