New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: net/http: context based graceful server shutdown #52805
Comments
This proposal passes a Context and a graceful shutdown timeout to The existing Under this proposal, therefore, we'd have one API that takes a I think this proposal is addressing two issues at the same time:
Perhaps it might be simpler to address the second point without introducing a context to |
Basically always? Context is the canonical way to control the lifetime of a call. This is why signal.NotifyContext was added to the standard library, because it was so common to want to hook these two things up. I wonder if the solution is to add graceful shutdown semantics right into context.Context, and then you'd only need ListenAndServeCtx, and the context would fully contain graceful exit semantics as well as simple cancel semantics. And this would also then give everyone who uses contexts a one-stop shop for graceful shutdowns. I think we steal the idea from Shutdown(ctx) - implement a separate context that only controls the lifetime of Shutdown logic. It'll be a context within a context, but if we can word things well, I think it'll be ok. // ContextForShutdown returns the Context that controls the lifetime of the logic that
// runs when ctx's Done channel is closed.
func ContextForShutdown(ctx Context) Context
// WithShutdownTimeout returns a context that puts a timeout on the context returned by
// ContextForShutdown, and a CancelFunc that closes the done channel on the shutdown context.
func WithShutdownTimeout(parent Context, timeout time.Duration) (ctx Context, cancelShutdown CancelFunc)
// WithShutdownCancel returns a context and a CancelFunc that will close the done
// channel on the context returned by ContextForShutdown for ctx.
func WithShutdownCancel(parent Context) (ctx Context, cancelShutdown CancelFunc) I use Shutdown here to try to avoid the confusion of reusing "Cancel". Shutdown being the logic that runs after the original context is marked done. // in user code, this is all we'd need for graceful shutdown of an application
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
ctx, cancelShutdown := context.WithShutdownTimeout(ctx, timeout)
defer cancelShutdown()
return server.ListenAndServeCtx(ctx) And then once the original context gets closed by the Signal, http.Server would call context.ContextForShutdown on the context it was passed by ListenAndServeCtx, and use that to pass into Shutdown. |
This proposal has been added to the active column of the proposals project |
I'm agnostic about the right API here, but I do think this is a common need and a bit of a PITA to code correctly. Maybe it accepts a callback to run when ctx1 is cancelled and that callback returns the context for the shutdown? |
In the event of a |
It sounds like having some kind of graceful shutdown is a common need that might be worth filling. People seem less enthusiastic about these specific API details. Does anyone want to propose a simpler way? |
I'm all ears (or eyes I guess) for proposing a simpler way 👍🏼 One alternative we can do is have Something like this:
and you would just call Personally I still prefer the Go convention of explicitly passing context as a method/function argument but there's precedent here with |
I think having I would rather see something like a version of |
I think that any proposal here needs to start with a clear goal. The original post here includes a function which shows how to run an
That doesn't look too bad to me. The least elegant part to my eyes is the handling of This argues to me that a context is not the right way to bound the lifetime of an |
There are already ListenAndServe, ListenAndServeTLS, Serve, ServeTLS. Adding more context variant methods seems bad, so adding struct fields seems like lesser evil. The approach of using callbacks is pretty flexible. Maybe BeforeFunc func(*http.Server) error (not sure if this is a good name). server := &http.Server{
Addr: addr,
Handler: handler,
BeforeFunc: func(s *http.Server) {
log.Print("starting up")
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<- c
log.Print("shutting down")
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
return s.Shutdown(ctx)
},
}
if err := server.ListenAndServeContext(ctx, time.Second); err != nil {
log.Fatal(err)
} |
I concur. My mental model of |
Is there anything to learn from https://github.com/facebookarchive/grace? It sounds like we are still waiting for the right approach. |
Nit: regarding graceful shutdown, |
I'm not sure how to move this discussion forward. Does anyone want to propose an alternative, simpler API? Alternately, http.Server already has Shutdown and Close. It's a few lines of code to connect those to whatever contexts or timeouts that might be needed in a given situation. And servers don't get started and stopped at high frequency, so an extra goroutine or two wouldn't matter. Do we really need more than those? Is there something else they don't provide that we should be making available? That is, maybe Shutdown and Close are the alternative, simpler API. |
I think the existing I do not think we should provide a version of It would be nice if
Instead of this:
Perhaps we could add a
|
I'm not sure if I understand the suggestion above, so I'd love to see a full end to end example of how the code would look like from the caller's side while handling all scenarios such as pre-run errors like port-already-in-use, and post-run errors such as shutdown-timeout. Also, telling people to ignore the error from ListenAndServe and to instead use Wait seems a little odd to me. I think I'd rather deprecate a method entirely in favor of a newer one as that will feel more clean cut than deprecating only the return part of an existing method signature. Also I'd love to mention that my reasoning behind this issue (and not particularly the proposal itself) is the fact that today if you want to "properly" run a server in production, you need to handle graceful shutdowns and therefore you need to write this code (or a variation of it) every single time: https://github.com/marwan-at-work/serverctx/blob/main/serverctx.go The code itself is not particularly long, but it is subtle and in retrospect it would have been great if the net/http library only exposed a single way of running/closing a server that was always graceful. Therefore, I am mostly interested in ways can steer the community to always consider graceful shutdowns when running production servers. Of course, adding new APIs can do just that but if there isn't a simple/clean way, maybe the solution is to just properly document/encourage people of this best practice? For example
That said, I'm definitely happy to see new API suggestions like the one above and how they would work. Thanks! |
I'm not sure about changing ListenAndServe to ever return nil (lots of code does log.Fatal(http.ListenAndServe(...)) and it would be weird to get log.Fatal(nil) out of that). But we could certainly have a distinguished ErrGracefulShutdown (bad name) or something like that. In any event, that sounds like a different proposal. Sounds like new API as described in this issue is a likely decline. |
Based on the discussion above, this proposal seems like a likely decline. |
No change in consensus, so declined. |
Proposal
I propose adding two new methods to the net/http *Server struct:
ListenAndServeContext
andListenAndServeTLSContext
that will handle gracefully shutting down the server upon a context cancellation signal.To accompany these two methods, we need a graceful shutdown timeout which can either live as a field in the Server struct, or an argument to the new methods.
The signature can either be:
OR
It might also be worth noting that there are two more methods (Serve, and ServeTLS) that can arguably take a context as well. I rarely ever encounter explicit net.Listeners being passed in when starting an http server so I'll leave that up for discussion.
The examples below will assume option 2 above as it makes it more explicit for the user to pass a Shutdown timeout instead of it being tucked away as a struct field.
Code Examples
The code from the caller's perspective will look something like this:
Implementation
The underlying implementation would abstract the subtle details to handle graceful shutdowns:
Notably, the implementation above would return a nil error when a server is gracefully and successfully shutdown without hitting the timeout. We could also explicitly return the ctx.Error() or a new error variable if we never want to return a nil error. But I think a nil error is a fine signal for saying a server was successfully terminated.
Finally, there's a question of what a
0
duration would mean:s.Shutdown
will immediately be cancelled.s.Shutdown(context.Background()
.The first option seems more rational though the second might be nice for convenience but it would have to be explicitly documented.
Rationale
Many people today run Go servers while not implementing graceful shutdown in the first place (for example calling the global http.ListenAndServe function) or implementing it but missing edge cases such as the following:
And probably other subtleties. I have myself made those mistakes many items and ended up abstracting the logic into its own context: https://github.com/marwan-at-work/serverctx/blob/main/serverctx.go
Looking at private code within my company I also noticed each team does a flavor of the above which seems like a good case for code re-use.
Furthermore, looking at the standard library we already have similar patterns such as
exec.Command
andexec.CommandContext
which could make this a natural fit for Go developers already familiar with these APIs.The text was updated successfully, but these errors were encountered: