Description
Proposal Details
Abstract
This proposal introduces WithInterrupt
, a new function in the context
package that provides an atomic-based, low-overhead cancellation check for performance-sensitive code.
Background
The context
package provides mechanisms for request-scoped cancellation, by checking ctx.Err()
or <-ctx.Done()
, but in tight loops these can introduce significant overhead.
A common workaround is to periodically check ctx.Err()
using a counter rather than in every iteration, but this introduces a trade-off between responsiveness and efficiency. A better approach is to use sync/atomic
to maintain a separate cancellation flag, but this both a non-obvious solution and must be implemented manually each time, though this code isn't that difficult to write once you understand the problem.
Proposal
Add WithInterrupt
to the context
package (implementation as an example)
// WithInterrupt returns a dirived context that pionts to the parent but has a new Done channel in a similar fasion to [WithCancel].
// The returned isDone function can be called to efficiently check the context's cancelation status.
// This is useful for tight loops that need an interrupt where checking `ctx.Err()` or `<-ctx.Done()` adds too much overhead.
//
// Like [WithCancel], canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this [Context] complete.
func WithInterrupt(parent Context) (ctx Context, isDone func() bool, cancel func()) {
var flag uint32
ctx, cancel = WithCancel(parent)
go func() {
<-ctx.Done()
atomic.StoreUint32(&flag, 1)
}()
isDone = func() bool {
return atomic.LoadUint32(&flag) != 0
}
return
}
Use Case Example
ctx, isDone, cancel := context.WithInterrupt(context.WithTimeout(context.Background(), 2*time.Second))
defer cancel()
for {
if isDone() {
return ctx.Err()
}
// Perform computation
}