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!