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
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
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
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
|