geerpc 在超时处理方面的 bug

Author: Phoenix500526Created Dec 7, 2020Updated Mar 27, 2025

在 geerpc 的 day4-timeout 中执行 go test -v 有一定概率会出现卡死的现象,具体日志如下:

bash
~/ExcellentOriginSource/7days-golang/gee-rpc/day4-timeout$ go test -v
=== RUN   TestClient_dialTimeout
=== PAUSE TestClient_dialTimeout
=== RUN   TestClient_Call
=== PAUSE TestClient_Call
=== RUN   TestNewService
2020/12/07 12:10:47 rpc server: register Foo.Sum
--- PASS: TestNewService (0.00s)
=== RUN   TestMethodType_Call
2020/12/07 12:10:47 rpc server: register Foo.Sum
--- PASS: TestMethodType_Call (0.00s)
=== CONT  TestClient_dialTimeout
=== CONT  TestClient_Call
2020/12/07 12:10:47 rpc server: register Bar.Timeout
=== RUN   TestClient_dialTimeout/timeout
=== RUN   TestClient_Call/client_timeout
=== RUN   TestClient_dialTimeout/0
=== RUN   TestClient_Call/server_handle_timeout
--- PASS: TestClient_dialTimeout (3.00s)
    --- PASS: TestClient_dialTimeout/timeout (1.00s)
    --- PASS: TestClient_dialTimeout/0 (2.00s)
# 在此处失去响应,需要手动中止程序或等待10m后程序崩溃退出

这种情况下,如果不进行中断,则会在 10 分钟后因创建过多 goroutine 而导致崩溃,出现这种问题的 bug 大概是每跑 5 次出现1 次. 我的代码运行环境为 Ubuntu-16.04 LTS, go 的版本为 version go1.15.4 linux/amd64 通过对问题代码进行定位,我发现出问题的地方是 client.go 的 Call 函数 中的 select 语句

go
func (client *Client) Call(ctx context.Context, serviceMethod string, args, reply interface{}) error {
	call := client.Go(serviceMethod, args, reply, make(chan *Call, 1))
	select {
	case <-ctx.Done():
		client.removeCall(call.Seq)
		return errors.New("rpc client: call failed: " + ctx.Err().Error())
	case call := <-call.Done:
		return call.Error
       }
}

在 select 语句中会出现 client.done() 未被执行的情况,这种情况下两个 case 都不满足,进而导致测试程序卡死 select 处。这个问题在 day7 处执行 go run main.go 时也有一定概率会出现,结果是导致 main.go 也卡死,陷入无响应状态