Java made a huge mistake of having no network timeouts. A network request can block a thread forever. Even Python did the same. The language designers should have chosen some conservative appropriate numbers instead.

What’s surprising is that the Go language repeated it!  Here’s a simple demo

Let’s first create a server that would block forever

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Usage: go run server.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func main() {
    startWebServer(8080)
}

func startWebServer(port int) {
    http.HandleFunc("/block-forever", blockForeverHandler)
    log.Printf("Serving on port %d", port)
    log.Fatal("%s", http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

func blockForeverHandler(w http.ResponseWriter, req *http.Request) {
    // Do nothing and block forever
    count := 0
    for true {
        time.Sleep(1 * time.Second)
        count++
        log.Printf("Blocked since %d seconds ago...", count)
    }
}

Start the server in one shell with `go run server.go`, you can test it out by visiting http://localhost:8080/block-forever or by doing `curl http://localhost:8080/block-forever` in the shell.

Now, connect to that server in another shell with the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Usage: go run client.go
package main

import (
    "log"
    "net/http"
)

func main() {
    fetchData("http://localhost:8080/block-forever")
}

func fetchData(urlStr string) {
    log.Printf("Fetching data from %s", urlStr)
    _, err := http.Get(urlStr)
    if err != nil {
        log.Fatalln(err)
    }
    log.Printf("Received data from %s", urlStr)
}

And wait for it complete, it won’t.

So what’s the fix? Add a timeout before making the request

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Usage: go run client.go
package main

import (
    "log"
    "net/http"
    "time"
)

func main() {
    fetchDataWithTimeout("http://localhost:8080/block-forever")
}

func fetchDataWithTimeout(urlStr string) {
    log.Printf("Fetching data from %s", urlStr)
    client := &http.Client{
        Timeout: 15 * time.Second,
    }
    _, err := client.Get(urlStr)
    if err != nil {
        log.Fatalln(err)
    }
    log.Printf("Received data from %s", urlStr)
}

This will demonstrate the correct behavior by failing with

Go
1
2
3
Fetching data from http://localhost:8080/block-forever
Get "http://localhost:8080/block-forever": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
exit status 1