if err := fd.init(); err != nil {
return nil, err
}
- if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() {
- fd.pfd.SetWriteDeadline(deadline)
+
+ if ctx.Done() != nil {
+ // Propagate the Context's deadline and cancellation.
+ // If the context is already done, or if it has a nonzero deadline,
+ // ensure that that is applied before the call to ConnectEx begins
+ // so that we don't return spurious connections.
defer fd.pfd.SetWriteDeadline(noDeadline)
+
+ if ctx.Err() != nil {
+ fd.pfd.SetWriteDeadline(aLongTimeAgo)
+ } else {
+ if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() {
+ fd.pfd.SetWriteDeadline(deadline)
+ }
+
+ done := make(chan struct{})
+ stop := context.AfterFunc(ctx, func() {
+ // Force the runtime's poller to immediately give
+ // up waiting for writability.
+ fd.pfd.SetWriteDeadline(aLongTimeAgo)
+ close(done)
+ })
+ defer func() {
+ if !stop() {
+ // Wait for the call to SetWriteDeadline to complete so that we can
+ // reset the deadline if everything else succeeded.
+ <-done
+ }
+ }()
+ }
}
+
if !canUseConnectEx(fd.net) {
err := connectFunc(fd.pfd.Sysfd, ra)
return nil, os.NewSyscallError("connect", err)
_ = fd.pfd.WSAIoctl(windows.SIO_TCP_INITIAL_RTO, (*byte)(unsafe.Pointer(¶ms)), uint32(unsafe.Sizeof(params)), nil, 0, &out, nil, 0)
}
- // Wait for the goroutine converting context.Done into a write timeout
- // to exist, otherwise our caller might cancel the context and
- // cause fd.setWriteDeadline(aLongTimeAgo) to cancel a successful dial.
- done := make(chan bool) // must be unbuffered
- defer func() { done <- true }()
- go func() {
- select {
- case <-ctx.Done():
- // Force the runtime's poller to immediately give
- // up waiting for writability.
- fd.pfd.SetWriteDeadline(aLongTimeAgo)
- <-done
- case <-done:
- }
- }()
-
// Call ConnectEx API.
if err := fd.pfd.ConnectEx(ra); err != nil {
select {
return fn(ctx, network, host)
}
testHookSetKeepAlive = func(time.Duration) {}
+
+ // testHookStepTime sleeps until time has moved forward by a nonzero amount.
+ // This helps to avoid flakes in timeout tests by ensuring that an implausibly
+ // short deadline (such as 1ns in the future) is always expired by the time
+ // a relevant system call occurs.
+ testHookStepTime = func() {}
)
"time"
)
+func init() {
+ // Install a hook to ensure that a 1ns timeout will always
+ // be exceeded by the time Dial gets to the relevant system call.
+ //
+ // Without this, systems with a very large timer granularity — such as
+ // Windows — may be able to accept connections without measurably exceeding
+ // even an implausibly short deadline.
+ testHookStepTime = func() {
+ now := time.Now()
+ for time.Since(now) == 0 {
+ time.Sleep(1 * time.Nanosecond)
+ }
+ }
+}
+
var dialTimeoutTests = []struct {
initialTimeout time.Duration
initialDelta time.Duration // for deadline