Skip to content
Open
33 changes: 27 additions & 6 deletions internal/xcontext/done.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,30 @@ import (
"time"
)

type doneCtx <-chan struct{}
type (
doneCtx <-chan struct{}
doneAlreadySignaledCtx struct {
context.Context //nolint:containedctx // thin wrapper delegating Deadline/Value
}
)

var (
noopCancel = func() {}
closedDoneChan = func() chan struct{} {
ch := make(chan struct{})
close(ch)

return ch
}()
Comment thread
asmyasnikov marked this conversation as resolved.
)

func (doneAlreadySignaledCtx) Done() <-chan struct{} {
return closedDoneChan
}

func (doneAlreadySignaledCtx) Err() error {
Comment thread
asmyasnikov marked this conversation as resolved.
Outdated
return context.Canceled
}
Comment thread
asmyasnikov marked this conversation as resolved.

func (done doneCtx) Deadline() (deadline time.Time, ok bool) {
return
Expand All @@ -29,16 +52,14 @@ func (done doneCtx) Value(key any) any {
}

func WithDone(parent context.Context, done <-chan struct{}) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(parent)

select {
case <-done:
cancel()

return ctx, cancel
return doneAlreadySignaledCtx{Context: parent}, noopCancel
default:
}
Comment thread
asmyasnikov marked this conversation as resolved.
Outdated

ctx, cancel := context.WithCancel(parent)

stop := context.AfterFunc(doneCtx(done), func() {
cancel()
})
Expand Down
41 changes: 41 additions & 0 deletions internal/xcontext/done_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,44 @@ func TestWithDone(t *testing.T) {
require.Error(t, ctx.Err())
})
}

// BenchmarkWithDone/AlreadyClosed-12 67398998 17.58 ns/op 16 B/op 1 allocs/op
// BenchmarkWithDone/Open_CancelImmediately-12 3005686 399.4 ns/op 424 B/op 8 allocs/op
// BenchmarkWithDone/Open_CloseDoneThenCancel-12 1301546 922.2 ns/op 648 B/op 10 allocs/op
func BenchmarkWithDone(b *testing.B) {
b.Run("AlreadyClosed", func(b *testing.B) {
done := make(chan struct{})
close(done)
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
ctx, cancel := WithDone(b.Context(), done)
cancel()
_ = ctx
}
})
Comment thread
asmyasnikov marked this conversation as resolved.

b.Run("Open_CancelImmediately", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
done := make(chan struct{})
ctx, cancel := WithDone(b.Context(), done)
cancel()
_ = ctx
_ = done
}
Comment thread
asmyasnikov marked this conversation as resolved.
})

b.Run("Open_CloseDoneThenCancel", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
done := make(chan struct{})
ctx, cancel := WithDone(b.Context(), done)
close(done)
<-ctx.Done()
cancel()
}
Comment thread
asmyasnikov marked this conversation as resolved.
})
}
Loading