-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwait.go
198 lines (184 loc) · 4.91 KB
/
wait.go
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Tideland Go Wait
//
// Copyright (C) 2019-2023 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.
package wait // import "tideland.dev/go/wait"
//--------------------
// IMPORTS
//--------------------
import (
"context"
"fmt"
"time"
)
//--------------------
// POLL
//--------------------
// ConditionFunc has to be implemented for checking the wanted condition. A positive
// condition will return true and nil, a negative false and nil. In case of failure
// during the check false and the error have to be returned. The function will
// be used by the poll functions.
type ConditionFunc func() (bool, error)
// Poll provides different ways to wait for conditions by polling. The conditions
// are checked by user defined functions with the signature
//
// func() (ok bool, err error)
//
// Here the bool return value signals if the condition is fulfilled, e.g. a file
// you're waiting for has been written into the according directory.
//
// This signal for check a condition is returned by a ticker with the signature
//
// func(ctx context.Context) <-chan struct{}
//
// The context is for signalling the ticker to end working, the channel for the signals.
// Pre-defined tickers support
//
// - simple constant intervals,
// - a maximum number of constant intervals,
// - a constant number of intervals with a deadline,
// - a constant number of intervals with a timeout, and
// - jittering intervals.
//
// The behaviour of changing intervals can be user-defined by
// functions with the signature
//
// func(in time.Duration) (out time.Duration, ok bool)
//
// Here the argument is the current interval, return values are the
// wanted interval and if the polling shall continue. For the predefined
// tickers according convenience functions named With...() exist.
//
// Example (waiting for a file to exist):
//
// // Tick every second for maximal 30 seconds.
// ticker := wait.MakeExpiringIntervalTicker(time.Second, 30*time.Second),
//
// // Check for existence of a file.
// condition := func() (bool, error) {
// _, err := os.Stat("myfile.txt")
// if err != nil {
// if os.IsNotExist(err) {
// return false, nil
// }
// return false, err
// }
// // Found file.
// return true, nil
// }
//
// // And now poll.
// wait.Poll(ctx, ticker, condition)
//
// From outside the polling can be stopped by cancelling the context.
func Poll(ctx context.Context, ticker TickerFunc, condition ConditionFunc) error {
tickCtx, cancel := context.WithCancel(context.Background())
defer cancel()
tickc := ticker(tickCtx)
for {
select {
case <-ctx.Done():
if ctx.Err() != nil {
return fmt.Errorf("context has been cancelled with error: %v", ctx.Err())
}
return nil
case _, open := <-tickc:
// Ticker sent a signal to check for condition.
if !open {
// Oh, ticker tells to end.
return fmt.Errorf("ticker exceeded while waiting for the condition")
}
ok, err := check(condition)
if err != nil {
// ConditionFunc has an error.
return fmt.Errorf("poll condition returned error: %v", err)
}
if ok {
// ConditionFunc is happy.
return nil
}
}
}
}
// WithInterval is convenience for Poll() with MakeIntervalTicker().
func WithInterval(
ctx context.Context,
interval time.Duration,
condition ConditionFunc,
) error {
return Poll(
ctx,
MakeIntervalTicker(interval),
condition,
)
}
// WithMaxIntervals is convenience for Poll() with MakeMaxIntervalsTicker().
func WithMaxIntervals(
ctx context.Context,
interval time.Duration,
max int,
condition ConditionFunc,
) error {
return Poll(
ctx,
MakeMaxIntervalsTicker(interval, max),
condition,
)
}
// WithDeadline is convenience for Poll() with MakeDeadlinedIntervalTicker().
func WithDeadline(
ctx context.Context,
interval time.Duration,
deadline time.Time,
condition ConditionFunc,
) error {
return Poll(
ctx,
MakeDeadlinedIntervalTicker(interval, deadline),
condition,
)
}
// WithTimeout is convenience for Poll() with MakeExpiringIntervalTicker().
func WithTimeout(
ctx context.Context,
interval, timeout time.Duration,
condition ConditionFunc,
) error {
return Poll(
ctx,
MakeExpiringIntervalTicker(interval, timeout),
condition,
)
}
// WithJitter is convenience for Poll() with MakeJitteringTicker().
func WithJitter(
ctx context.Context,
interval time.Duration,
factor float64,
timeout time.Duration,
condition ConditionFunc,
) error {
return Poll(
ctx,
MakeJitteringTicker(interval, factor, timeout),
condition,
)
}
//--------------------
// PRIVATE HELPER
//--------------------
// check runs the condition catching potential panics and returns
// them as failure.
func check(condition ConditionFunc) (ok bool, err error) {
defer func() {
if r := recover(); r != nil {
ok = false
err = fmt.Errorf("panic during condition check: %v", r)
}
}()
ok, err = condition()
return
}
// EOF