labstack / echo

High performance, minimalist Go web framework

Home Page:https://echo.labstack.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Passing a context.Context into echo.Start()

UlrichEckhardt opened this issue · comments

Issue Description

The context.Context object passed around through calls is an almost fundamental (and IMHO revolutionary) feature of Go. In particular, a call might spawn various goroutines in the background, which might need to be shut down at some point, for which you can use cancellation. The example I'm having issues to implement with labstack/echo is that I want to catch signals (think Control-C or Docker container shutdown) and shut down the HTTP service gracefully.

Generally, I'd expect to be able to pass in the context as parameter, but echo doesn't support this. I have dug through existing issues a bit, and found some issues about context.Context interoperability:

Most of these are rather concerned with having the standard context in code handling a request. Passing the context through the whole call tree is just one way (and IMHO the correct way) to make it available in the handler code.

My questions here are:

  • Is there a way to pass in a standard context that I'm missing?
  • Is there an alternative way for shutdown and other things usually implemented using the context?
  • Are there plans to implement passing in a context?

Version/commit

4.11.2

Here: https://echo.labstack.com/docs/start-server

Basically - echo.Start* are pretty much leftovers from times when Echo supported multiple engines. Start* methods are quite limited and you will pretty much always turn to use plain http.Server to start HTTP server.

See: https://github.com/golang/go/blob/8da6405e0db80fa0a4136fb816c7ca2db716c2b2/src/net/http/server.go#L2810 for different fields ala *Timeout that you would want to configure.

func main() {
	e := echo.New()
	// add middleware and routes
	// ...
	s := http.Server{
		Addr:    ":8080",
		Handler: e,
		//ReadTimeout: 30 * time.Second, // customize http.Server timeouts
	}
	if err := s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
		log.Fatal(err)
	}
}

For graceful shutdown see https://echo.labstack.com/docs/cookbook/graceful-shutdown

func main() {
	e := echo.New()

	e.GET("/", func(c echo.Context) error {
		time.Sleep(5 * time.Second)
		return c.JSON(http.StatusOK, "OK")
	})
	
	quit, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
	defer cancel()

	s := http.Server{
		Addr:    ":8080",
		Handler: e,
		//ReadTimeout: 30 * time.Second, // customize http.Server timeouts
	}
	go func(srv *http.Server) {
		if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
			log.Fatal(err)
		}
		cancel() // in case server returns before ctrl+c
	}(&s)
	
	// Wait until interrupt signal to start shutdown
	<-quit.Done()

	// start gracefully shutdown with a timeout of 10 seconds.
	ctx, cancelGC := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancelGC()

	if err := s.Shutdown(ctx); err != nil {
		log.Fatal(err)
	}
}

@UlrichEckhardt can this be considered answered/solved?

Hello @aldas,

thank you for your response! In particular, the link to the docs is very valuable, can you add a link in the README.md? I believe others will find that useful, too. :)

Concerning the graceful shutdown, that part is answered and fixes my immediate issues. However, that was just one example, the general question remains: Why or why not pass in a context and pass this context (or derived contexts) to the requests as they are handled. And yes, I am aware that that would be an API break, so it's probably not going to be done quickly. I'm more interested in understanding, since I'm still a rookie in Go.

Greetings from Hamburg!

Uli

Why or why not pass in a context and pass this context (or derived contexts) to the requests as they are handled

Passing a context would only be useful to initiate shutdown. You can not use same context to be parent for request contextes as the moment your root context is cancelled the child contexes would be too - meaning all request would end immediately and this is not "graceful".

p.s. maybe this #2205 (comment) helps to understand how server handles contextes. You can follow the links there and see actual implementation.


We are not touching existing Start* methods due to semantic versioning and because using

	s := http.Server{
		Addr:    ":8080",
		Handler: e,
	}

is more useful as you have access to all those other http.Server public fields which are in the long run what you want to fine tune.

Basically e.Start() is only useful for small demos. In real world use http.Server aproach.

"You can not use same context to be parent for request contextes as the moment your root context is cancelled the child contexes would be too" -- that and the fact that the default http.Server is used explains this.

Thanks!