Cancelling a connection

In order to test out the usage of context in a TCP connection, we can create a goroutine with a TCP server that will wait a period of time before starting the listener:

addr := os.Args[1]
go func() {
time.Sleep(time.Second)
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalln("Listener:", addr, err)
}
c, err := listener.Accept()
if err != nil {
log.Fatalln("Listener:", addr, err)
}
defer c.Close()
}()

We can use a context with a timeout that's lower than the server waiting time. We have to use net.Dialer in order to use the context in a dial operation:

ctx, canc := context.WithTimeout(context.Background(),   
time.Millisecond*100)
defer canc()
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", os.Args[1])
if err != nil {
log.Fatalln("-> Connection:", err)
}
log.Println("-> Connection to", os.Args[1])
conn.Close()

The application will try to connect for a short time, but will eventually give up when the context expires, returning an error.

In a situation where you want to establish a single connection from a series of endpoints, context cancellation would be a perfect use case. All the connection attempts would share the same context, and the first connection that dials correctly would call the cancellation, stopping the other attempts. We will create a single server that is listening to one of the addresses we will try to call:

list := []string{
"localhost:9090",
"localhost:9091",
"localhost:9092",
}
go func() {
listener, err := net.Listen("tcp", list[0])
if err != nil {
log.Fatalln("Listener:", list[0], err)
}
time.Sleep(time.Second * 5)
c, err := listener.Accept()
if err != nil {
log.Fatalln("Listener:", list[0], err)
}
defer c.Close()
}()

Then, we can try to dial all three addresses and cancel the context as soon as one connects. We will use a WaitGroup to synchronize with the end of the goroutines:

ctx, canc := context.WithTimeout(context.Background(), time.Second*10)
defer canc()
wg := sync.WaitGroup{}
wg.Add(len(list))
for _, addr := range list {
go func(addr string) {
defer wg.Done()
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", addr)
if err != nil {
log.Println("-> Connection:", err)
return
}
log.Println("-> Connection to", addr, "cancelling context")
canc()
conn.Close()
}(addr)
}
wg.Wait()

What we will see in the output of this program is one connection succeeding, followed by the cancellation error of the other attempt.