Python asyncio.run() 卡死排查实录:一行 socketpair() 引发的血案
一段在另一台电脑上完美运行的 MCP Server 代码,拷到自己的 Windows 10 后执行直接挂起——Ctrl+C 杀不掉。重装 Python 3.12/3.13(exe 和 zip 方式都试了),换 FastMCP 版本,查了数小时 AI Chat,无一能解。最终通过系统性逐层拆解,找到了一个让人意想不到的根因。
1. 症状
PS D:\Projects> python time_server.py
# ← 没有任何输出,直接卡死
# ← Ctrl+C 无反应
# ← 进程不可终止,只能任务管理器强杀
代码极简——一个基于 FastMCP 的 MCP Server:
from fastmcp import FastMCP
app = FastMCP("time-server")
@app.tool()
def get_now(tz: str = "UTC", fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
"""获取当前时间"""
...
if __name__ == "__main__":
app.run()
在另一台同版本 Win10 22H2 的电脑上,这段代码会正常打印 FastMCP banner 然后进入 stdio 等待——标准 MCP Server 行为。
2. 走过的弯路
排查过程中,以下假设全部被验证后排除:
| 假设 | 操作 | 结果 |
|---|---|---|
| WindowsSelectorEventLoopPolicy 导致卡死 | 删除该代码 | 仍卡,排除 |
| FastMCP 版本差异 | 降级到 3.2.4 | 仍卡,排除 |
| trio 后端绕过 asyncio | 安装 trio,强制 anyio 用 trio | trio.run 也卡死,排除 |
| ucrtbase.dll 版本过旧 | 检查版本 10.0.19041 vs 系统 build 19045 | 实际正常,排除 |
| Python 版本/安装方式 | 重装 3.12 exe/zip、3.13 exe/zip | 全卡,排除 |
| TAP-Windows VPN 网卡劫持 loopback | 禁用 + 卸载 | 仍卡,排除 |
6 条弯路,数小时,全部排除。 问题不在 Python、不在库版本、不在 UCRT——在更底层。
3. 逐层拆解:从 asyncio.run 到 socket.socketpair
关键转折点:把 asyncio.run() 的每一步拆开,每步写文件确认执行位置。
Step 0: script started ✅
Step 1: import asyncio ✅
Step 2: asyncio.new_event_loop() ❌ ← 卡死在这!
asyncio.new_event_loop() 就卡了!进一步拆解 ProactorEventLoop 构造函数:
IocpProactor() ✅ (IOCP 端口创建正常)
socket.socketpair() ❌ ← 卡死!
卡死点定位到 socket.socketpair()——Python 最底层的网络 IPC 函数。
再拆 socket.socketpair() 的实现(它在 127.0.0.1 上创建临时 TCP server + client):
socket.bind('127.0.0.1', 0) ✅
socket.listen() ✅
socket.connect('127.0.0.1', port) ❌ ← 永久阻塞!
TCP connect 到 127.0.0.1 死锁。 这已经不是 Python 的问题了。
4. 跨语言验证:PowerShell 也卡
$listener = [System.Net.Sockets.TcpListener]::new([IPAddress]::Loopback, 0)
$listener.Start()
$client = [System.Net.Sockets.TcpClient]::new()
$client.Connect("127.0.0.1", $port) # ← 卡死!
C# 的 TcpClient 连 localhost 也卡——Windows 系统级的 TCP loopback connect 被破坏了。
5. 关键对照实验
| 操作 | 结果 | 含义 |
|---|---|---|
ping 127.0.0.1 |
✅ 正常 | ICMP 不受影响 |
socket.bind('127.0.0.1', 0) |
✅ 正常 | bind 不需要入站连接 |
socket.connect('127.0.0.1', port) |
❌ 永久阻塞 | TCP 入站连接被拦截 |
| 关闭 Windows 防火墙 | socket.socketpair() ✅ 立即返回 |
防火墙是拦截者 |
| 添加 loopback 规则 + 开启防火墙 | ✅ 正常运行 | 规则生效,loopback 被放行 |
6. 根因揭晓
火绒安全软件拦截了 TCP loopback 入站连接。
完整链路:
火绒 WFP 驱动注入过滤规则
→ 拦截 127.0.0.1 TCP 入站连接
→ socket.socketpair() 卡死(connect 到 localhost 被阻断)
→ ProactorEventLoop.__init__() 卡死(_make_self_pipe() 调 socketpair)
→ asyncio.new_event_loop() 卡死
→ asyncio.run() 卡死
→ 所有 MCP Server / asyncio 程序卡死
→ Ctrl+C 无法穿透 WFP 层面的 IO 阻塞
火绒(进程名 HipsDaemon / HipsTray / wsctrlsvc)通过 Windows Filtering Platform (WFP) 驱动注入网络过滤规则,其优先级高于 Windows 自带防火墙。火绒的默认策略将 127.0.0.1 的 TCP 入站连接视为可疑并拦截——而 Windows 原生防火墙默认不拦截 loopback。
另一台电脑正常运行,很可能是未安装火绒,或火绒版本不同已有 loopback 白名单。
7. 修复
立即生效的修复(管理员 PowerShell)
# 添加 loopback TCP 放行规则
New-NetFirewallRule -DisplayName 'Allow Loopback TCP Inbound' `
-Direction Inbound -Action Allow -Protocol TCP -LocalAddress 127.0.0.1 -Profile Any
New-NetFirewallRule -DisplayName 'Allow Loopback TCP Outbound' `
-Direction Outbound -Action Allow -Protocol TCP -RemoteAddress 127.0.0.1 -Profile Any
# 重新启用防火墙(关闭仅用于测试,不建议长期关闭)
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
火绒侧配置(防止重启后规则被覆盖)
- 打开火绒 → 安全设置 → 网络防护 → 自定义规则
- 添加:允许 127.0.0.1 ↔ 127.0.0.1 的 TCP 连接(入站 + 出站)
- 或直接关闭火绒的"网络防护"模块(个人电脑不需要拦截本地 loopback)
一行验证
python -c "import socket; a,b=socket.socketpair(); print('OK'); a.close(); b.close()"
输出 OK 即修好。卡死则说明火绒覆盖了 Windows 规则,需在火绒内加白名单。
8. 反思
这次排查的核心教训:
当所有"常见原因"都排除后,不要继续在应用层打转——往下拆一层,拆到操作系统调用级别。
asyncio.run() 卡死 → 看起来是 asyncio 的 bug → 实际上是 socket.socketpair() 卡死 → 看起来是 Python socket 的 bug → 实际上是 Windows TCP loopback connect 死锁 → 看起来是系统 bug → 实际上是安全软件的防火墙规则拦截了本该放行的流量。
每一层都比上一层更难想到,但每一层的验证只需要几行代码。关键方法是逐步写文件追踪——在每一步执行后立即将结果写入文件,这样即使进程卡死被强杀,也能从文件中看到最后成功执行到哪一步。
另外,ping 127.0.0.1 能通不代表 TCP loopback 能通——ICMP 和 TCP 是完全不同的协议栈,防火墙可以分别拦截。这个陷阱会让很多人误判"网络没问题"。
9. 影响范围
不仅是 Python asyncio,以下场景都会被此问题影响:
- Node.js:部分框架的 cluster 模式使用 localhost IPC
- Docker Desktop:容器与宿主机通信依赖 loopback
- 数据库本地连接:MySQL/PostgreSQL 的 localhost 模式
- 任何使用
socket.socketpair()的程序 - 任何 TCP 连接到 127.0.0.1 的程序
如果你的 Windows 程序莫名卡死在"连接"阶段,先跑那一行验证脚本。
排查日期:2026-05-16 | 环境:Windows 10 Home China 22H2 (Build 19045) + 火绒 6.0
欢迎关注公众号"飞鸿影记(fhyblog)",探寻物件背后的逻辑,记录生活真实的影子。

作者:飞鸿影
出处:http://52fhy.cnblogs.com/
版权申明:没有标明转载或特殊申明均为作者原创。本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处。


浙公网安备 33010602011771号