主题
Day 7 技术设计文档 — 超时与容错
超时控制架构
连接超时实现
go
func dialTimeout(f newClientFunc, network, address string, opts ...*codec.Option) (*Client, error) {
opt := parseOptions(opts...)
conn, err := net.DialTimeout(network, address, opt.ConnectTimeout)
if err != nil {
return nil, err
}
ch := make(chan clientResult, 1)
go func() {
client, err := f(conn, opt)
ch <- clientResult{client, err}
}()
select {
case <-time.After(opt.ConnectTimeout):
conn.Close()
return nil, fmt.Errorf("connect timeout")
case result := <-ch:
return result.client, result.err
}
}双重超时保护:net.DialTimeout 控制 TCP 握手超时,外层 time.After 控制协议协商超时。
处理超时实现
Context 传播
go
func (client *Client) Call(ctx context.Context, ...) error {
call := client.Go(...)
select {
case <-ctx.Done():
client.removeCall(call.Seq) // 清理 pending map
return ctx.Err()
case c := <-call.Done:
return c.Error
}
}注意:context 超时时,必须从 pending map 中移除 Call,否则后续收到的迟到响应会导致 removeCall 返回 nil(这是正常的)。
竞态条件分析
服务端 handleRequest 中存在一个微妙的竞态:超时后发送了错误响应,但处理 goroutine 仍可能完成并尝试发送正常响应。通过 called 和 sent channel 协调:
- 超时分支只在
called未关闭时发送错误响应 - 正常分支通过
sent通知主流程"我已经发送完了" sendingmutex 保证两个分支不会同时写入