Add AfterTimeout to Deadline - #396
Conversation
9a2ebc9 to
784a23b
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #396 +/- ##
==========================================
+ Coverage 84.18% 84.46% +0.27%
==========================================
Files 41 41
Lines 3396 3418 +22
==========================================
+ Hits 2859 2887 +28
+ Misses 398 393 -5
+ Partials 139 138 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
6129112 to
5b8ecf7
Compare
| if setTo.IsZero() { | ||
| d.pending-- | ||
| d.state = deadlineStopped | ||
| clear(d.cbs) |
There was a problem hiding this comment.
clearing d.cbs here breaks the context.afterFuncer contract - there's no way to disambiguate "already fired" from "discarded" when the cancel func returns false. it also orphans derived contexts eg. from context.WithCancel(d).
There was a problem hiding this comment.
Fair enough, if we consider putting to 0 was akin to be indefinitely running, however this indeed breaks the case where the deadline would be reset after that.
However I was under the impression here we are supporting the stopped state since we have this deadlineStopped state. Maybe we should not support stopping then? (either we are starting or started or exceeded)
| assert.Equal(t, expectedCalls, calls, "Wrong order of deadline signal") | ||
| }) | ||
|
|
||
| t.Run("DeadlineAfterFuncExceedFuture", func(t *testing.T) { |
There was a problem hiding this comment.
these can probably be a table test eg.{name, ops func(*Deadline, time.Time), want int32} with additional cases to cover AfterFunc(f) while already exceeded, context.WithCancel(d) cancel propagation, and multiple callbacks.
There was a problem hiding this comment.
I have added tests for already exceeded and multi callbacks
Cancel is out of Deadline scope if I am not mistaken?
That's the whole reason we need AfterFunc-like behavior actually
Happy to move the tests into a table, but does it not make error tracing harder?
| // AfterFunc attaches a function to the deadline. | ||
| // The functions will be triggered on deadline exceeded. | ||
| // If the deadline is reset, the functions are skipped. | ||
| // Attached functions are only triggered once. |
There was a problem hiding this comment.
the wording is unclear because there is no "reset" function and Set(zero) and Set(futureTime) have opposite effects on the pending callbacks. the cancel func semantics should be documented. it should be stated that this implements the context.afterFuncer interface so someone doesn't change the signature later and silently break it
There was a problem hiding this comment.
I have reworded the whole accordingly (and also renamed the function as per Jo's comment)
| done := d.done | ||
| for _, cb := range d.cbs { | ||
| go cb() | ||
| } | ||
| clear(d.cbs) | ||
| d.mu.Unlock() | ||
|
|
||
| close(done) |
There was a problem hiding this comment.
my code here is wrong: context.go:111
// If Done is not yet closed, Err returns nil.close(d.done) should move into the critical section above the dispatch loop so callbacks cannot observe done unclosed.
There was a problem hiding this comment.
Interesting, I now wonder if this is the root cause of the issue that triggered this whole PR.
I was using this struct directly with context.AfterFunc but was hitting panics because of Err() returning nil.
Let me double check that once again, maybe we can discard the whole PR and only keep the fix
There was a problem hiding this comment.
So actually by design Deadline cannot be used for context.AfterFunc because of it's mutable nature, whereas context.AfterFunc relies on the fact the context cannot change once it's "done".
So we still need our own API, as proposed
There was a problem hiding this comment.
it's worse than i originally thought - implementations of context.Context must be monotonic state machines. stdlib can panic when a context is derived from a Deadline that is subsequently reset whether it implements context.afterFuncer or not. here is a failing test demonstrating the bug: gist
There was a problem hiding this comment.
@paulwe this is pre-existing, Set already violates the context.Context contract, and the panic can already happen, the linked dtls pr shouldn't trigger this panic with AfterTimeout but since this is public I support fixing this and pushing a new transport major release if needed.
| d.Set(now.Add(10 * time.Millisecond)) | ||
| <-time.After(20 * time.Millisecond) | ||
| d.Set(now.Add(10 * time.Millisecond)) | ||
| <-time.After(20 * time.Millisecond) |
There was a problem hiding this comment.
10/20ms margins will probably flake in CI. also double check that this is taking the expected path...
There was a problem hiding this comment.
Ideally we should be able to control a fake clock but I have simply copied from above tests.
Maybe I can do better with channel synchronization instead, otherwise do you suggest higher timers?
There was a problem hiding this comment.
usually 100-200ms is enough slack for oversubscribed gh test runners. if the numbers you chose are found elsewhere in the package i retract my suggestion though
There was a problem hiding this comment.
We should start using synctest for time tests like this, it should be beind a 1.25 go build flag.
|
@noboruma I thought about this a lot and the dtls pr l, I think the cleanest approach is to avoid implementing context.afterFuncer on Deadline. Could this instead expose something like OnDeadline or AfterDeadline have DTLS use that directly? This also give us the freedom to use a different notify design other than a callback. |
5983fa3 to
16b97bc
Compare
16b97bc to
f106894
Compare
|
So with #397 this PR is now obsolete. |
Add
AfterFunctodeadlineto allow for continuation on when deadline are reached.Convenient for avoiding long running
goroutines with connection contexts.This change is needed for: pion/dtls#1096