@@ -40,6 +40,10 @@ func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding
40
40
var timeCh <- chan time.Time
41
41
doneCh := ctx .Done ()
42
42
43
+ if ! sliding {
44
+ timeCh = t .C ()
45
+ }
46
+
43
47
// if immediate is true the condition is
44
48
// guaranteed to be executed at least once,
45
49
// if we haven't requested immediate execution, delay once
@@ -50,17 +54,27 @@ func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding
50
54
}(); err != nil || ok {
51
55
return err
52
56
}
53
- } else {
57
+ }
58
+
59
+ if sliding {
54
60
timeCh = t .C ()
61
+ }
62
+
63
+ for {
64
+
65
+ // Wait for either the context to be cancelled or the next invocation be called
55
66
select {
56
67
case <- doneCh :
57
68
return ctx .Err ()
58
69
case <- timeCh :
59
70
}
60
- }
61
71
62
- for {
63
- // checking ctx.Err() is slightly faster than checking a select
72
+ // IMPORTANT: Because there is no channel priority selection in golang
73
+ // it is possible for very short timers to "win" the race in the previous select
74
+ // repeatedly even when the context has been canceled. We therefore must
75
+ // explicitly check for context cancellation on every loop and exit if true to
76
+ // guarantee that we don't invoke condition more than once after context has
77
+ // been cancelled.
64
78
if err := ctx .Err (); err != nil {
65
79
return err
66
80
}
@@ -77,21 +91,5 @@ func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding
77
91
if sliding {
78
92
t .Next ()
79
93
}
80
-
81
- if timeCh == nil {
82
- timeCh = t .C ()
83
- }
84
-
85
- // NOTE: b/c there is no priority selection in golang
86
- // it is possible for this to race, meaning we could
87
- // trigger t.C and doneCh, and t.C select falls through.
88
- // In order to mitigate we re-check doneCh at the beginning
89
- // of every loop to guarantee at-most one extra execution
90
- // of condition.
91
- select {
92
- case <- doneCh :
93
- return ctx .Err ()
94
- case <- timeCh :
95
- }
96
94
}
97
95
}
0 commit comments