1// Package deadline implements the deadline (also known as "timeout") resiliency pattern for Go.
2package deadline
3
4import (
5	"errors"
6	"time"
7)
8
9// ErrTimedOut is the error returned from Run when the deadline expires.
10var ErrTimedOut = errors.New("timed out waiting for function to finish")
11
12// Deadline implements the deadline/timeout resiliency pattern.
13type Deadline struct {
14	timeout time.Duration
15}
16
17// New constructs a new Deadline with the given timeout.
18func New(timeout time.Duration) *Deadline {
19	return &Deadline{
20		timeout: timeout,
21	}
22}
23
24// Run runs the given function, passing it a stopper channel. If the deadline passes before
25// the function finishes executing, Run returns ErrTimeOut to the caller and closes the stopper
26// channel so that the work function can attempt to exit gracefully. It does not (and cannot)
27// simply kill the running function, so if it doesn't respect the stopper channel then it may
28// keep running after the deadline passes. If the function finishes before the deadline, then
29// the return value of the function is returned from Run.
30func (d *Deadline) Run(work func(<-chan struct{}) error) error {
31	result := make(chan error)
32	stopper := make(chan struct{})
33
34	go func() {
35		result <- work(stopper)
36	}()
37
38	select {
39	case ret := <-result:
40		return ret
41	case <-time.After(d.timeout):
42		close(stopper)
43		return ErrTimedOut
44	}
45}
46