2018-07-15 17:36:53 +02:00
|
|
|
package util
|
|
|
|
|
|
|
|
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 := deadline.Sub(time.Now())
|
|
|
|
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
|
|
|
|
}
|