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
7package net
8
9import (
10	"context"
11	"syscall"
12	"testing"
13	"time"
14)
15
16// Issue 16523
17func 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