// setRequestCancel sets the Cancel field of req, if deadline is // non-zero. The RoundTripper's type is used to determine whether the legacy // CancelRequest behavior should be used. func setRequestCancel(req *Request, rt RoundTripper, deadline time.Time) (stopTimer func(), wasCanceled func() bool) { if deadline.IsZero() { return nop, alwaysFalse } initialReqCancel := req.Cancel // the user's original Request.Cancel, if any cancel := make(chan struct{}) req.Cancel = cancel wasCanceled = func() bool { select { case <-cancel: return true default: return false } } doCancel := func() { // The new way: close(cancel) // The legacy compatibility way, used only // for RoundTripper implementations written // before Go 1.5 or Go 1.6. type canceler interface { CancelRequest(*Request) } switch v := rt.(type) { case *Transport, *http2Transport: // Do nothing. The net/http package's transports // support the new Request.Cancel channel case canceler: v.CancelRequest(req) } } stopTimerCh := make(chan struct{}) var once sync.Once stopTimer = func() { once.Do(func() { close(stopTimerCh) }) } timer := time.NewTimer(time.Until(deadline)) go func() { select { case <-initialReqCancel: doCancel() timer.Stop() case <-timer.C: doCancel() case <-stopTimerCh: timer.Stop() } }() return stopTimer, wasCanceled }
func setDeadlineImpl(fd *netFD, t time.Time, mode int) error { diff := int64(time.Until(t)) d := runtimeNano() + diff if d <= 0 && diff > 0 { // If the user has a deadline in the future, but the delay calculation // overflows, then set the deadline to the maximum possible value. d = 1<<63 - 1 } if t.IsZero() { d = 0 } if err := fd.incref(); err != nil { return err } runtime_pollSetDeadline(fd.pd.runtimeCtx, d, mode) fd.decref() return nil }
// WithDeadline returns a copy of the parent context with the deadline adjusted // to be no later than d. If the parent's deadline is already earlier than d, // WithDeadline(parent, d) is semantically equivalent to parent. The returned // context's Done channel is closed when the deadline expires, when the returned // cancel function is called, or when the parent context's Done channel is // closed, whichever happens first. // // Canceling this context releases resources associated with it, so code should // call cancel as soon as the operations running in this Context complete. func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { // The current deadline is already sooner than the new one. return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: deadline, } propagateCancel(parent, c) d := time.Until(deadline) if d <= 0 { c.cancel(true, DeadlineExceeded) // deadline has already passed return c, func() { c.cancel(true, Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(d, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } }
// DialWithDialer connects to the given network address using dialer.Dial and // then initiates a TLS handshake, returning the resulting TLS connection. Any // timeout or deadline given in the dialer apply to connection and TLS // handshake as a whole. // // DialWithDialer interprets a nil configuration as equivalent to the zero // configuration; see the documentation of Config for the defaults. func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error) { // We want the Timeout and Deadline values from dialer to cover the // whole process: TCP connection and TLS handshake. This means that we // also need to start our own timers now. timeout := dialer.Timeout if !dialer.Deadline.IsZero() { deadlineTimeout := time.Until(dialer.Deadline) if timeout == 0 || deadlineTimeout < timeout { timeout = deadlineTimeout } } var errChannel chan error if timeout != 0 { errChannel = make(chan error, 2) time.AfterFunc(timeout, func() { errChannel <- timeoutError{} }) } rawConn, err := dialer.Dial(network, addr) if err != nil { return nil, err } colonPos := strings.LastIndex(addr, ":") if colonPos == -1 { colonPos = len(addr) } hostname := addr[:colonPos] if config == nil { config = defaultConfig() } // If no ServerName is set, infer the ServerName // from the hostname we're connecting to. if config.ServerName == "" { // Make a copy to avoid polluting argument or default. c := config.Clone() c.ServerName = hostname config = c } conn := Client(rawConn, config) if timeout == 0 { err = conn.Handshake() } else { go func() { errChannel <- conn.Handshake() }() err = <-errChannel } if err != nil { rawConn.Close() return nil, err } return conn, nil }
func (c *timerCtx) String() string { return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline)) }