Repository navigation
Expand file tree
/
Copy pathtimeout.go
More file actions
53 lines (43 loc) · 1.55 KB
/
Copy pathtimeout.go
File metadata and controls
53 lines (43 loc) · 1.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package runnable
import (
"context"
"errors"
"fmt"
"time"
)
// Timeout returns a runnable that cancels the given runnable when it runs for
// longer than d.
//
// The result of the runnable is returned as is, so a runnable that stops
// cleanly with nil on the timeout succeeds. When it returns the bare
// [context.DeadlineExceeded] of the timeout, Timeout returns
// "timed out after <d>: context deadline exceeded" instead, which still wraps
// it. A cancellation of the parent context is not a timeout.
//
// runnable.Schedule(runnable.Timeout(time.Minute, job), runnable.Every(time.Hour))
func Timeout(d time.Duration, r Runnable) Runnable {
return &timeout{name: "timeout/" + runnableName(r), timeout: d, runnable: r}
}
type timeout struct {
name string
timeout time.Duration
runnable Runnable
}
var _ Runnable = (*timeout)(nil)
func (r *timeout) runnableName() string { return r.name }
// errTimedOut is the cause of the context cancelled by [Timeout], to tell it
// apart from a deadline of the parent context.
var errTimedOut = errors.New("timed out")
func (r *timeout) Run(ctx context.Context) error {
ctx, cancel := context.WithTimeoutCause(ctx, r.timeout, errTimedOut)
defer cancel()
err := r.runnable.Run(ctx)
if !errors.Is(context.Cause(ctx), errTimedOut) {
return err
}
logger.Info(resolveName(ctx, r.name)+": timed out", "timeout", r.timeout)
if err == context.DeadlineExceeded { //nolint:errorlint // only the bare ctx.Err() is replaced
return fmt.Errorf("timed out after %s: %w", r.timeout, context.DeadlineExceeded)
}
return err
}