zrepl/util/optionaldeadline/optionaldeadline.go

84 lines
1.7 KiB
Go
Raw Normal View History

package optionaldeadline
2018-07-15 17:36:53 +02:00
import (
"context"
"sync"
2018-08-25 21:30:25 +02:00
"time"
2018-07-15 17:36:53 +02:00
)
type contextWithOptionalDeadline struct {
context.Context
2018-08-25 21:30:25 +02:00
m sync.Mutex
2018-07-15 17:36:53 +02:00
deadline time.Time
done chan struct{}
2018-08-25 21:30:25 +02:00
err error
2018-07-15 17:36:53 +02:00
}
func (c *contextWithOptionalDeadline) Deadline() (deadline time.Time, ok bool) {
c.m.Lock()
defer c.m.Unlock()
return c.deadline, !c.deadline.IsZero()
}
func (c *contextWithOptionalDeadline) Err() error {
c.m.Lock()
defer c.m.Unlock()
return c.err
}
2018-08-25 21:30:25 +02:00
func (c *contextWithOptionalDeadline) Done() <-chan struct{} {
2018-07-15 17:36:53 +02:00
return c.done
}
func ContextWithOptionalDeadline(pctx context.Context) (ctx context.Context, enforceDeadline func(deadline time.Time)) {
// mctx can only be cancelled by cancelMctx, not by a potential cancel of pctx
rctx := &contextWithOptionalDeadline{
Context: pctx,
2018-08-25 21:30:25 +02:00
done: make(chan struct{}),
err: nil,
2018-07-15 17:36:53 +02:00
}
enforceDeadline = func(deadline time.Time) {
// Set deadline and prohibit multiple calls
rctx.m.Lock()
alreadyCalled := !rctx.deadline.IsZero()
if !alreadyCalled {
rctx.deadline = deadline
}
rctx.m.Unlock()
if alreadyCalled {
return
}
// Deadline in past?
sleepTime := time.Until(deadline)
2018-07-15 17:36:53 +02:00
if sleepTime <= 0 {
rctx.m.Lock()
rctx.err = context.DeadlineExceeded
rctx.m.Unlock()
close(rctx.done)
return
}
go func() {
// Set a timer and wait for timer or parent context to be cancelled
timer := time.NewTimer(sleepTime)
var setErr error
select {
case <-pctx.Done():
timer.Stop()
setErr = pctx.Err()
case <-timer.C:
setErr = context.DeadlineExceeded
}
rctx.m.Lock()
rctx.err = setErr
rctx.m.Unlock()
close(rctx.done)
}()
}
return rctx, enforceDeadline
}