[goroutine leaks] http.TimeoutHandler does not kill respective ServeHTTP goroutine

Timeout handler moves ServeHTTP execution on a new goroutine, but not able to kill that goroutine after the timer ends. On every request, it creates two goroutines, but ServeHTTP goroutines never kill with context.

package main

import (
	"fmt"
	"io"
	"net/http"
	"runtime"
	"time"
)

type api struct{}

func (a api) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	i := 0
	for {
		if i == 500 {
			break
		}
		//fmt.Printf("@time: %s\n", time.Now())
		fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
		time.Sleep(1 * time.Second)
		i++
	}
	_, _ = io.WriteString(w, "Hello World!")
}

func main() {
	var a api
	s := http.NewServeMux()
	s.Handle("/", a)
	h := http.TimeoutHandler(s, 1*time.Second, `Timeout`)
	
	fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())

	_ = http.ListenAndServe(":8080", h)
}
1 Like

Your handler is not stoppable from outside. You have to implement cancellation context in your handler. TimeoutHandler doesn’t/can’t kill the routine, it cancels the context and responses with 503 Service Unavailable error. You need to handle it yourself.

package main

import (
	"fmt"
	"io"
	"net/http"
	"runtime"
	"time"
)

type api struct{}

func (a api) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	i := 0
	for {
		if i == 500  {
			break
		}
		//fmt.Printf("@time: %s\n", time.Now())
		fmt.Printf("i: %d #goroutines: %d\n", i, runtime.NumGoroutine())
		time.Sleep(1 * time.Second)
		select {
		case <-req.Context().Done():
			{
				fmt.Println("context canceled")
				return
			}
		default:
		}
		i++
	}
	_, _ = io.WriteString(w, "Hello World!")
}

func main() {
	var a api
	s := http.NewServeMux()
	s.Handle("/", a)
	h := http.TimeoutHandler(s, 10*time.Second, `Timeout`)

	fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())

	_ = http.ListenAndServe(":8081", h)
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.