Я читал "Создание микросервисов с помощью 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
}
}
1-й вопрос
// 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.
Я не понимаю, , что закрывает канал-заглушку для анонимной программы, которая все еще может работать Я думаю, что когда канал-пробка закрыт, нижележащая процедура может все еще работать.
go func() {
result <- work(stopper)
}()
(Пожалуйста, исправьте меня, если я ошибаюсь здесь) Я думаю, что после close(stopper)
эта процедура вызоветtakesTwentyMillis
(= рабочая функция) со стопорным каналом в качестве параметра.И функция продолжит работу и будет спать в течение 20 миллисекунд и вернет ноль, чтобы перейти к каналу результата.И main () заканчивается здесь, верно?
Я не вижу, в чем смысл здесь закрывать канал пробки.Функция takesTwentyMillis
, похоже, в любом случае не использует канал в теле функции: (.
2-й вопрос
// 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
... но поскольку я не понимаю этот код, я не понимаю, почему этокод существует в первую очередь (то есть, что этот код пытается проверить, и как он выполняется, и т. д.).
3-й (дополнительный) вопрос относительно 2-го вопроса
ИтакЯ понимаю, что когда функция Run
во втором вопросе вызывает закрытие канала ограничителя, рабочая функция получает сигнал. И рабочий закрывает завершенный канал и возвращает ноль. Я использовал delve (= go debugger), чтобы увидеть это, иGDB доставляет меня к goroutine через deadline.go
после строки return nil
.
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
, который затем должен быть передан в канал результатов, верно?Но эта строка, похоже, не выполняется по какой-то причине.
Я знаю, что основная процедура, которая является функцией Run
, уже вернула ErrTimedOut.Так что я думаю, это как-то связано с этим?