我目前正在从事一项小型服务。从我的测试中,我编写的代码在与上下文有关的某些情况下有可能泄漏go例程。有没有一种好的和/或惯用的方法来对此进行补救?我在下面提供一些示例代码。
func Handle(ctx context.Context, r *Req) (*Response, error) {
ctx, cancel := context.WithTimeout(ctx, time.Second * 5)
defer cancel()
resChan := make(chan Response)
errChan := make(chan error)
go process(r, resChan, errChan)
select {
case ctx.Done():
return nil, ctx.Err()
case res := <-resChan:
return &res, nil
case err := <-errChan:
return nil, err
}
}
func process(r *Req, resChan chan<- Response, errChan chan<- error) {
defer close(errChan)
defer close(resChan)
err := doSomeWork()
if err != nil {
errChan <- err
return
}
err = doSomeMoreWork()
if err != nil {
errChan <- err
return
}
res := Response{}
resChan <- res
}
假设地,如果客户端取消了上下文,或者超时发生在流程函子有机会在未缓冲的通道之一(resChan,errChan)上发送之前,则Handle不会剩下任何通道读取器并在通道上发送会无限期封锁,没有读者。由于在这种情况下进程不会返回,因此通道也不会关闭。
我想出了process2作为解决方案,但是我不禁以为自己做错了什么,或者有更好的方法来解决这个问题。
func process2(ctx context.Context, r *Req, resChan chan<- Response, errChan chan<- error) {
defer close(errChan)
defer close(resChan)
err := doSomeWork()
select {
case <-ctx.Done():
return
default:
if err != nil {
errChan <- err
return
}
}
err = doSomeMoreWork()
select {
case <-ctx.Done():
return
default:
if err != nil {
errChan <- err
return
}
}
res := Response{}
select{
case <-ctx.Done():
return
default:
resChan <- res
}
}
此方法可确保每次尝试发送频道时,首先检查上下文是否已完成或取消。如果是这样,则它不会尝试发送并返回。我很确定这可以解决在第一个流程函子中发生的所有go例程泄漏。
有更好的方法吗?也许我全都错了。