Skip to content

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 仍可能完成并尝试发送正常响应。通过 calledsent channel 协调:

  • 超时分支只在 called 未关闭时发送错误响应
  • 正常分支通过 sent 通知主流程"我已经发送完了"
  • sending mutex 保证两个分支不会同时写入