WithTimeout
WithTimeout
会在一个goroutine中开启一个定时器,到期之后,自动取消对应的ctx
,同时,所有从该ctx
派生的子ctx
都会被取消。
package ctxtest
import (
"context"
"testing"
"time"
)
// go test -run TestCtx -v deprecated_loader/ctxtest/*.go
func TestCtx(t *testing.T) {
ctx := context.Background()
// WithTimeout will start a timer, after which ctx's Done is closed
ctx, cancel := context.WithTimeout(ctx, 1000*time.Millisecond)
defer cancel()
time.Sleep(500 * time.Millisecond)
err := ctx.Err()
if err != nil {
t.Fatalf("expect ctx err to be nil, actaul:%v", err)
}
time.Sleep(510 * time.Millisecond)
err2 := ctx.Err()
if err2 == nil {
t.Fatalf("expect ctx err to be not nil after timeout")
}
t.Logf("err2:%v", err2)
}
WithTimeout(0)
你可能会好奇,当WithTimeout
的timeout
参数是0,或者负数时,会有什么表现。实际上,表现就是,在返回的时候,该context已经超时取消了。因为,实际上timeout
的作用是用于确定deadline,即deadline = now + timeout
,所以timeout <= 0
时,只会使得deadline
已经到达,直接被cancel掉(参考context源码,下面第21行)。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
// ******************** time.Until(d) = d - now **************
// 当 d - now < 0, 即d < now时,此处直接调用cancel,不需要计时器的参与
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
测试代码:
package util
import (
"context"
"testing"
"time"
)
// go test -run TestZeroTimeout -v util/*.go
func TestZeroTimeout(t *testing.T) {
ctx := context.Background()
cancelCtx, cancel := context.WithTimeout(ctx, time.Duration(0)*time.Millisecond)
defer cancel()
select {
case <-cancelCtx.Done():
t.Logf("done") // prints: done
}
}