| // Copyright 2016 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build unix | |
| package net | |
| import ( | |
| "context" | |
| "errors" | |
| "syscall" | |
| "testing" | |
| "time" | |
| ) | |
| func init() { | |
| isEADDRINUSE = func(err error) bool { | |
| return errors.Is(err, syscall.EADDRINUSE) | |
| } | |
| } | |
| // Issue 16523 | |
| func TestDialContextCancelRace(t *testing.T) { | |
| oldConnectFunc := connectFunc | |
| oldGetsockoptIntFunc := getsockoptIntFunc | |
| oldTestHookCanceledDial := testHookCanceledDial | |
| defer func() { | |
| connectFunc = oldConnectFunc | |
| getsockoptIntFunc = oldGetsockoptIntFunc | |
| testHookCanceledDial = oldTestHookCanceledDial | |
| }() | |
| ln := newLocalListener(t, "tcp") | |
| listenerDone := make(chan struct{}) | |
| go func() { | |
| defer close(listenerDone) | |
| c, err := ln.Accept() | |
| if err == nil { | |
| c.Close() | |
| } | |
| }() | |
| defer func() { <-listenerDone }() | |
| defer ln.Close() | |
| sawCancel := make(chan bool, 1) | |
| testHookCanceledDial = func() { | |
| sawCancel <- true | |
| } | |
| ctx, cancelCtx := context.WithCancel(context.Background()) | |
| connectFunc = func(fd int, addr syscall.Sockaddr) error { | |
| err := oldConnectFunc(fd, addr) | |
| t.Logf("connect(%d, addr) = %v", fd, err) | |
| if err == nil { | |
| // On some operating systems, localhost | |
| // connects _sometimes_ succeed immediately. | |
| // Prevent that, so we exercise the code path | |
| // we're interested in testing. This seems | |
| // harmless. It makes FreeBSD 10.10 work when | |
| // run with many iterations. It failed about | |
| // half the time previously. | |
| return syscall.EINPROGRESS | |
| } | |
| return err | |
| } | |
| getsockoptIntFunc = func(fd, level, opt int) (val int, err error) { | |
| val, err = oldGetsockoptIntFunc(fd, level, opt) | |
| t.Logf("getsockoptIntFunc(%d, %d, %d) = (%v, %v)", fd, level, opt, val, err) | |
| if level == syscall.SOL_SOCKET && opt == syscall.SO_ERROR && err == nil && val == 0 { | |
| t.Logf("canceling context") | |
| // Cancel the context at just the moment which | |
| // caused the race in issue 16523. | |
| cancelCtx() | |
| // And wait for the "interrupter" goroutine to | |
| // cancel the dial by messing with its write | |
| // timeout before returning. | |
| select { | |
| case <-sawCancel: | |
| t.Logf("saw cancel") | |
| case <-time.After(5 * time.Second): | |
| t.Errorf("didn't see cancel after 5 seconds") | |
| } | |
| } | |
| return | |
| } | |
| var d Dialer | |
| c, err := d.DialContext(ctx, "tcp", ln.Addr().String()) | |
| if err == nil { | |
| c.Close() | |
| t.Fatal("unexpected successful dial; want context canceled error") | |
| } | |
| select { | |
| case <-ctx.Done(): | |
| case <-time.After(5 * time.Second): | |
| t.Fatal("expected context to be canceled") | |
| } | |
| oe, ok := err.(*OpError) | |
| if !ok || oe.Op != "dial" { | |
| t.Fatalf("Dial error = %#v; want dial *OpError", err) | |
| } | |
| if oe.Err != errCanceled { | |
| t.Errorf("DialContext = (%v, %v); want OpError with error %v", c, err, errCanceled) | |
| } | |
| } |