我一直在阅读“使用go构建微服务”,该书介绍了apache/go-resiliency/deadline
软件包来处理超时。
deadline.go
// Package deadline implements the deadline (also known as "timeout") resiliency pattern for Go.
package deadline
import (
"errors"
"time"
)
// ErrTimedOut is the error returned from Run when the deadline expires.
var ErrTimedOut = errors.New("timed out waiting for function to finish")
// Deadline implements the deadline/timeout resiliency pattern.
type Deadline struct {
timeout time.Duration
}
// New constructs a new Deadline with the given timeout.
func New(timeout time.Duration) *Deadline {
return &Deadline{
timeout: timeout,
}
}
// Run runs the given function, passing it a stopper channel. If the deadline passes before
// the function finishes executing, Run returns ErrTimeOut to the caller and closes the stopper
// channel so that the work function can attempt to exit gracefully. It does not (and cannot)
// simply kill the running function, so if it doesn't respect the stopper channel then it may
// keep running after the deadline passes. If the function finishes before the deadline, then
// the return value of the function is returned from Run.
func (d *Deadline) Run(work func(<-chan struct{}) error) error {
result := make(chan error)
stopper := make(chan struct{})
go func() {
result <- work(stopper)
}()
select {
case ret := <-result:
return ret
case <-time.After(d.timeout):
close(stopper)
return ErrTimedOut
}
}
deadline_test.go
package deadline
import (
"errors"
"testing"
"time"
)
func takesFiveMillis(stopper <-chan struct{}) error {
time.Sleep(5 * time.Millisecond)
return nil
}
func takesTwentyMillis(stopper <-chan struct{}) error {
time.Sleep(20 * time.Millisecond)
return nil
}
func returnsError(stopper <-chan struct{}) error {
return errors.New("foo")
}
func TestDeadline(t *testing.T) {
dl := New(10 * time.Millisecond)
if err := dl.Run(takesFiveMillis); err != nil {
t.Error(err)
}
if err := dl.Run(takesTwentyMillis); err != ErrTimedOut {
t.Error(err)
}
if err := dl.Run(returnsError); err.Error() != "foo" {
t.Error(err)
}
done := make(chan struct{})
err := dl.Run(func(stopper <-chan struct{}) error {
<-stopper
close(done)
return nil
})
if err != ErrTimedOut {
t.Error(err)
}
<-done
}
func ExampleDeadline() {
dl := New(1 * time.Second)
err := dl.Run(func(stopper <-chan struct{}) error {
// do something possibly slow
// check stopper function and give up if timed out
return nil
})
switch err {
case ErrTimedOut:
// execution took too long, oops
default:
// some other error
}
}
// in deadline_test.go
if err := dl.Run(takesTwentyMillis); err != ErrTimedOut {
t.Error(err)
}
我在理解上述代码的执行流程时遇到问题。据我了解,因为takesTwentyMillis
函数的睡眠时间比设置的10毫秒超时时间长,
// in deadline.go
case <-time.After(d.timeout):
close(stopper)
return ErrTimedOut
time.After发出当前时间,并选择这种情况。然后,关闭塞子通道并返回ErrTimeout。
我不明白的是, 关闭塞子通道会对可能仍在运行的匿名goroutine起作用 我认为,当塞子通道关闭时,以下goroutine可能仍在运行。
go func() {
result <- work(stopper)
}()
(如果我在这里错了,请纠正我)我想在close(stopper)
之后,此goroutine将调用takesTwentyMillis
(=工作函数),其停止通道为参数。函数将继续运行并睡眠20毫秒,然后返回nil传递到结果通道。并且main()在这里结束,对吧?
我看不到此处关闭塞子通道的意义。 takesTwentyMillis
函数似乎并没有使用函数体内的通道:(。
// in deadline_test.go within TestDeadline()
done := make(chan struct{})
err := dl.Run(func(stopper <-chan struct{}) error {
<-stopper
close(done)
return nil
})
if err != ErrTimedOut {
t.Error(err)
}
<-done
这是我不完全理解的部分。我认为运行dl.Run
时,将初始化塞子通道。但是因为停止器通道中没有值,所以该函数调用将在<-stopper
处被阻塞...但是由于我不理解此代码,因此我不明白为什么此代码最初存在(即该代码正在尝试进行测试以及如何执行等)。
因此我了解到,当第二个问题中的Run
函数触发塞子通道关闭时,worker函数将获得信号。然后工人关闭完成的通道并返回nil。
我使用delve(= go调试器)进行了查看,gdb将我带到deadline.go
行之后return nil
中的goroutine。
err := dl.Run(func(stopper <-chan struct{}) error {
<-stopper
close(done)
--> return nil
})
键入n进入下一行后,delve将我带到这里
go func() {
--> result <- work(stopper)
}()
到此进程就结束了,因为当我再次输入n时,命令行会提示PASS并退出进程。为什么流程在这里完成? work(stopper)
似乎返回nil
,然后应该将其传递给结果通道吗?但是由于某些原因,该行似乎没有执行。
我知道主要的goroutine,即Run
函数,已经返回了ErrTimedOut。所以我想这与此有关吗?
答案 0 :(得分:1)
第一个问题
使用stopper
通道是为了向功能发出信号,例如takesTwentyMillis
已到了截止日期,呼叫者不再在乎其结果。通常,这意味着诸如takesTwentyMillis
之类的辅助函数应检查stopper
通道是否已关闭,以便可以取消其工作。尽管如此,检查stopper
通道仍然是工作程序功能的选择。它可能会也可能不会检查频道。
func takesTwentyMillis(stopper <-chan struct{}) error {
for i := 0; i < 20; i++ {
select {
case <-stopper:
// caller doesn't care anymore might as well stop working
return nil
case <-time.After(time.Second): // simulating work
}
}
// work is done
return nil
}
第二个问题
Deadline.Run()
的这一部分将关闭塞子通道。
case <-time.After(d.timeout):
close(stopper)
在关闭的通道(<-stopper
)上读取将立即为该通道返回零值。我认为这只是测试一个工作者功能,最终超时。