-
Notifications
You must be signed in to change notification settings - Fork 18k
net/http: Shutdown must make all future Serves return errors #20239
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
Comments
Hi @mrwonko, in order to follow Go's conventions on issues: could you please change the subject of the issue to: Thanks |
I'm busy this week and don't have time to look at this. Can somebody distill this down to a short summary for me? |
tldr: Because |
ListenAndServe is a convenience function. If you don't find it convenient, do the Listen and Serve (https://golang.org/pkg/net/http/#Server.Serve) steps separately. |
But I still haven't read this enough to understand the problem so I might be replying prematurely, though. |
|
I believe the issue is that there's no simple way to know if the server has started. If the shutdown is happening immediately after the This could be worked around with a custom listener (https://play.golang.org/p/x15ZPYYmfo), but I think it would be useful if there were a |
As @quentinmit wrote in #16220 (comment)
Turns out adding a ServeWithContext() might now be necessary given the raciness of current Shutdown() solution |
@bradfitz Here is a version which just uses https://play.golang.org/p/LOD878em-R When you trigger a context switch it works.
|
One way to work around this for now is to use a wait group in reverse. I think you can still have a context switch between srv := &http.Server{}
var isUp sync.WaitGroup
isUp.Add(1)
go func() {
isUp.Done()
if err := srv.Serve(l); err != nil {...}
}()
isUp.Wait()
// safe to call srv.Shutdown() from here on |
I think there is another, related race condition: a connection can be accepted in Serve but not closed by Shutdown. The sequence is something like this:
The fix would be to wait for Serve to exit before returning from Shutdown. |
I stumbled across this issue, so I took a few hours taking a look. I investigated the interaction between First, I did see a somewhat surprising behavior when I tried to run code similar to #20239 (comment). You would expect the server to stop and The reason for this is that while
The really tricky part is that From the perspective of a user who is creating a simple Web server, you would expect However, this reset is there to allow re-using the Server even after var srv http.Server
l1, err := newListener(...)
go srv.Serve(l1)
srv.Close()
// ... later ...
l2, err := newListener(...)
go srv.Serve(l2) It's fairly easy to make We have a situation where we are sharing http.Server (global state) between multiple goroutines, and their behavior is timing dependent as to when you call Given this fact and that we have a backwards compatible guarantee, I think we should be looking at introducing a
l1, ... := newListener(addr1)
l2, ... := newListener(addr2)
ctx, cancel := context.WithCancel()
go srv.ServeCtx(ctx, l1)
go srv.ServeCtx(ctx, l2)
// ... later ...
cancel() // kills both previous
// if you need to reuse srv, you can, by using a new context
go srv.ServeCtx(ctx2, l3) Maybe inclusion of such API is planned, I don't know. But meanwhile, without a new API, I don't think this an easy to fix problem. So I'd like to suggest documenting that it is the users' responsibility to synchronize calling If people are up for (1) documentation and (2) context.Context aware API, I'm willing to work on either. |
This is a fundamental mistake in the Shutdown API. The idea that you can Shutdown a Server and then once it's done call Serve again is wrong. We should make it be that once you Shutdown a server, it's shut down. Future calls to Serve simply return errShutdown. If this is too invasive for now it would be fine to punt to Go 1.11 but there's really no other way it can work. Any other way, you have this race between concurrent Shutdown and Serve behaving unpredictably. It's only a few lines of code to make shutdown sticky, and there's a decent argument for making this possibly-breaking change now instead of letting Shutdown last another release in the racy form (it was introduced in Go 1.8), just to head off 6 more months of people potentially thinking they can Serve again after Shutdown. /cc @tombergan @bradfitz to decide about Go 1.10 vs Go 1.11 |
(The needed decision is what milestone.) |
Change https://golang.org/cl/81778 mentions this issue: |
I'm trying to understand better this issue and I have a few questions:
This CL doesn't seem to achieve it:
func (srv *Server) SetKeepAlivesEnabled(v bool) {
// [CUT]
// Close HTTP/2 conns, as soon as they become idle, but reset
// the chan so future conns (if the listener is still active)
// still work and don't get a GOAWAY immediately, before their
// first request:
srv.mu.Lock()
defer srv.mu.Unlock()
srv.closeDoneChanLocked() // closes http2 conns
srv.doneChan = nil Proposal:
This should fix mentioned problems and simplify a bit, without changing any behavior that is currently predictable. I run a test program spawning 3 goroutines per http.Server (2 calling
With the proposed modifications all 200,000 |
@pam4, let's do that conversion to |
@bradfitz Thanks for your reply, I have a few concerns about the CL:
serveDone := make(chan struct{})
defer close(serveDone)
// If the *Server is being reused after a previous
// Close or Shutdown, reset its doneChan:
if len(s.listeners) == 0 && len(s.activeConn) == 0 {
s.doneChan = nil
}
// Close HTTP/2 conns, as soon as they become idle, but reset
// the chan so future conns (if the listener is still active)
// still work and don't get a GOAWAY immediately, before their
// first request:
srv.mu.Lock()
defer srv.mu.Unlock()
srv.closeDoneChanLocked() // closes http2 conns
srv.doneChan = nil
|
Replies on Gerrit would be easier, but thanks for the comments.
No, looks unused. I'll send out a change to delete it.
Nice catch. Will remove. And yes, the doneChan stuff is for http2. Or, it was. That changed in https://go-review.googlesource.com/43230 which is only for Go 1.9+. So, yeah, the "closes http2 conns" comment doesn't seem accurate anymore. I'll file a bug. |
Change https://golang.org/cl/122820 mentions this issue: |
Per comments in #20239 (comment) Updates #20239 Updates #26303 Change-Id: Iddf34c0452bd30ca9111b951bca48d1e011bd85a Reviewed-on: https://go-review.googlesource.com/122820 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
Per comments in golang/go#20239 (comment) Updates #20239 Updates #26303 Change-Id: Iddf34c0452bd30ca9111b951bca48d1e011bd85a Reviewed-on: https://go-review.googlesource.com/122820 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
What version of Go are you using (
go version
)?1.8.1
What operating system and processor architecture are you using (
go env
)?darwin amd64, but this is a general issue I also encounter in docker containers
What did you do?
I'm trying to reliably gracefully shut down an http Server: https://play.golang.org/p/ESmgJ6O_WU
However, because
ListenAndServe()
blocks while the Server is running, there is no way to ensure its call happens before a call toShutdown()
, soShutdown()
may be called beforeListenAndServe()
, in which case it returnsnil
and the later call toListenAndServe()
still starts a server that is then never shut down.What did you expect to see?
I expect to have some way of knowing that the server is now up and running such that
Shutdown()
will stop it, short of sending it http requests. Maybe an interface closer tohttptest.Server
.What did you see instead?
It seems I either need to repeatedly do http requests to the server to ensure it is up before calling
Shutdown()
or repeatedly callShutdown()
untilListenAndServe()
returns, like this: https://play.golang.org/p/LPfpz0LaBYNote that due to the pre-emptive nature of Goroutines, since
Shutdown()
can never be called on the same Goroutine asListenAndServe()
, this theoretically affects all programs usingShutdown()
without a loop.The text was updated successfully, but these errors were encountered: