正确写法
对于要控制超时的函数,要异步调用它,才能做到超时控制
go
func TestCtxCancal(t *testing.T) {
tag := "TestCtxCancal"
ctx := context.Background()
// kafka关闭可能耗时较长,设置强制退出时间,设置5s超时
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
stop := make(chan struct{}, 1)
go func() {
// mock kafka close 耗时很长,耗时15s
time.Sleep(time.Second * 15)
logger.Infow(ctx, tag, "msg", "kafka has closed")
stop <- struct{}{}
}()
hasCancelTimeout := false
select {
case <-ctx.Done():
logger.Error(context.TODO(), "sercer.main", "kafka Shutdown timed out, might not have stopped gracefully")
hasCancelTimeout = true
case <-stop:
logger.Info(context.TODO(), "main", "kafka consume has stopped")
hasCancelTimeout = false
}
//校验代码进入了select的超时case
assert.True(t, hasCancelTimeout)
}
错误写法
同步调用要控制超时的函数,就会导致必须等到函数执行完后,才走到select的代码,超时控制失去作用
go
func TestCtxCancalFail(t *testing.T) {
tag := "TestCtxCancal"
ctx := context.Background()
// kafka关闭可能耗时较长,设置强制退出时间
timeout := 5 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
stop := make(chan struct{}, 1)
start := time.Now()
// 如果同步调用close,那么监听超时代码就失去意义
{
// mock kafka close 耗时很长
time.Sleep(time.Second * 15)
logger.Infow(ctx, tag, "msg", "kafka has closed")
stop <- struct{}{}
}
select {
case <-ctx.Done():
logger.Error(context.TODO(), "sercer.main", "kafka Shutdown timed out, might not have stopped gracefully")
case <-stop:
logger.Info(context.TODO(), "main", "kafka consume has stopped")
}
elapsed := time.Since(start)
// 证明同步调用要控制超时的函数,cancel的超时控制就失去意义了
// 10s已经远大于 timeout := 5 * time.Second
assert.True(t, elapsed > 10*time.Second)
}
总结
go的ctx超时控制常用于服务的优雅退出,在释放资源时,加一个超时控制,避免服务迟迟未能退出。在使用context.WithTimeout
时,要注意正确异步调用要控制超时的函数