gRPC场景下WSARecv返回WSAEFAULT错误的原因排查
问题解答
1. 可能触发WSARecv返回WSAEFAULT的原因
- 传入的
WSABUF数组指针、单个WSABUF的buf缓冲区指针、bytes_read/flags/OVERLAPPED等输出参数指针指向无效内存,或对应内存区域无合法读写权限 WSABUF数组的计数参数大于实际分配的数组长度,导致内核访问数组越界- 异步重叠IO场景下,提交
WSARecv时传入的WSABUF数组、缓冲区内存,在IO操作完成前被释放、修改或覆盖 - 多线程场景下,
tcp->read_slices指向的切片缓冲区被其他线程异步修改、释放,导致读取到的切片指针、长度异常
2. Winsock追踪日志显示缓冲区参数为0、但gRPC本地追踪取值正常的原因
你遇到的是典型的异步IO栈内存生命周期不匹配问题:
代码中定义的WSABUF buffers[MAX_WSABUF_COUNT]是win_read函数的栈上局部变量。你在提交重叠IO版本的WSARecv请求后,函数会直接执行完成返回,栈内存会被后续调用的其他函数覆盖、回收。
Winsock内核态AFD驱动的日志采集是异步执行的,当驱动读取buffers数组内容生成日志时,原来的栈内存已经被覆盖为全0值,所以日志输出的BufferCount、Buffer指针、BufferLength均为0。而你添加的gRPC追踪是在WSARecv调用前后、函数返回前执行的,此时栈上buffers数组还未被覆盖,所以取值正常。
你观察到的「清零buffers后第二次调用大概率成功」的现象,本质是清零操作额外消耗了少量CPU时间,刚好错开了栈内存被覆盖的时序窗口,属于偶发的巧合,不是根本解决方案。
补充根因与修复建议
该问题的核心根因为提交异步重叠IO请求时使用了生命周期短于IO请求的栈内存,WSARecv的重叠IO操作会在内核态持续持有buffers数组的指针,直到IO完成,而栈内存在函数返回后就会失效,导致内核访问非法内存触发WSAEFAULT。修复方案可将WSABUF数组改为存放在tcp或grpc_winsocket结构体的堆内存中,保证其生命周期覆盖整个异步IO请求的周期。
内容的提问来源于stack exchange,提问作者Jacob B
相关产品推荐
相关产品推荐

