mirror of
https://github.com/crazybber/go-pattern-examples.git
synced 2024-11-25 13:16:02 +03:00
add timeout pattern
This commit is contained in:
parent
c3aebef2ff
commit
2f23bc271f
49
gomore/deadline/deadline.go
Normal file
49
gomore/deadline/deadline.go
Normal file
@ -0,0 +1,49 @@
|
||||
// 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() {
|
||||
value := work(stopper)
|
||||
select {
|
||||
case result <- value:
|
||||
case <-stopper:
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case ret := <-result:
|
||||
return ret
|
||||
case <-time.After(d.timeout):
|
||||
close(stopper)
|
||||
return ErrTimedOut
|
||||
}
|
||||
}
|
65
gomore/deadline/deadline_test.go
Normal file
65
gomore/deadline/deadline_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user