Home | History | Annotate | Download | only in net
      1 // Copyright 2016 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // +build darwin dragonfly freebsd linux netbsd openbsd solaris
      6 
      7 package net
      8 
      9 import (
     10 	"context"
     11 	"syscall"
     12 	"testing"
     13 	"time"
     14 )
     15 
     16 // Issue 16523
     17 func TestDialContextCancelRace(t *testing.T) {
     18 	oldConnectFunc := connectFunc
     19 	oldGetsockoptIntFunc := getsockoptIntFunc
     20 	oldTestHookCanceledDial := testHookCanceledDial
     21 	defer func() {
     22 		connectFunc = oldConnectFunc
     23 		getsockoptIntFunc = oldGetsockoptIntFunc
     24 		testHookCanceledDial = oldTestHookCanceledDial
     25 	}()
     26 
     27 	ln, err := newLocalListener("tcp")
     28 	if err != nil {
     29 		t.Fatal(err)
     30 	}
     31 	listenerDone := make(chan struct{})
     32 	go func() {
     33 		defer close(listenerDone)
     34 		c, err := ln.Accept()
     35 		if err == nil {
     36 			c.Close()
     37 		}
     38 	}()
     39 	defer func() { <-listenerDone }()
     40 	defer ln.Close()
     41 
     42 	sawCancel := make(chan bool, 1)
     43 	testHookCanceledDial = func() {
     44 		sawCancel <- true
     45 	}
     46 
     47 	ctx, cancelCtx := context.WithCancel(context.Background())
     48 
     49 	connectFunc = func(fd int, addr syscall.Sockaddr) error {
     50 		err := oldConnectFunc(fd, addr)
     51 		t.Logf("connect(%d, addr) = %v", fd, err)
     52 		if err == nil {
     53 			// On some operating systems, localhost
     54 			// connects _sometimes_ succeed immediately.
     55 			// Prevent that, so we exercise the code path
     56 			// we're interested in testing. This seems
     57 			// harmless. It makes FreeBSD 10.10 work when
     58 			// run with many iterations. It failed about
     59 			// half the time previously.
     60 			return syscall.EINPROGRESS
     61 		}
     62 		return err
     63 	}
     64 
     65 	getsockoptIntFunc = func(fd, level, opt int) (val int, err error) {
     66 		val, err = oldGetsockoptIntFunc(fd, level, opt)
     67 		t.Logf("getsockoptIntFunc(%d, %d, %d) = (%v, %v)", fd, level, opt, val, err)
     68 		if level == syscall.SOL_SOCKET && opt == syscall.SO_ERROR && err == nil && val == 0 {
     69 			t.Logf("canceling context")
     70 
     71 			// Cancel the context at just the moment which
     72 			// caused the race in issue 16523.
     73 			cancelCtx()
     74 
     75 			// And wait for the "interrupter" goroutine to
     76 			// cancel the dial by messing with its write
     77 			// timeout before returning.
     78 			select {
     79 			case <-sawCancel:
     80 				t.Logf("saw cancel")
     81 			case <-time.After(5 * time.Second):
     82 				t.Errorf("didn't see cancel after 5 seconds")
     83 			}
     84 		}
     85 		return
     86 	}
     87 
     88 	var d Dialer
     89 	c, err := d.DialContext(ctx, "tcp", ln.Addr().String())
     90 	if err == nil {
     91 		c.Close()
     92 		t.Fatal("unexpected successful dial; want context canceled error")
     93 	}
     94 
     95 	select {
     96 	case <-ctx.Done():
     97 	case <-time.After(5 * time.Second):
     98 		t.Fatal("expected context to be canceled")
     99 	}
    100 
    101 	oe, ok := err.(*OpError)
    102 	if !ok || oe.Op != "dial" {
    103 		t.Fatalf("Dial error = %#v; want dial *OpError", err)
    104 	}
    105 	if oe.Err != ctx.Err() {
    106 		t.Errorf("DialContext = (%v, %v); want OpError with error %v", c, err, ctx.Err())
    107 	}
    108 }
    109