On 2016-10-27 19:32, Nick Patavalis wrote:
type Server interface {
        Serve() error // start serving and block until exit
        Shutdown()    // async req. to shutdown, must not block
}

The "subtle races" I was talking about:

What happens if you call "Shutdown()" before you call Serve()?

Technically that depends on the Server implementation.
But in the provided http.Server extension, the answer is "nothing":

func (srv *Server) Shutdown() {
        srv.runlock.Lock()
        defer srv.runlock.Unlock()
        if !srv.running {
                return
        }
        <-srv.shutdown
}


If the
answer is "nothing" then there is necessarily a small window of time,
after calling "Serve()" when shutdown calls will be ignored.

Yes... but the semantics are clear. Shutdown() is an sync signal which will be ignored by servers not in a running state.

The only other well defined semantics I can see is that any Shutdown() on a Server will make any current and future Serve() exit.
But this would prevent a restart of the Server.

So the
answer *has* to be: "the server will stop immediately after it's
started" (i.e. the server will "remember" the shutdown). Fine so far.

That's a nother way to define the semantics yes - but I don't see it as something which "has" to be. Think of the Shutdown() signal as a datagram message. It might get lost if the server is not in a running state.

But you could easily implement a Server satisfying the same interface, behaving as you suggest - making the Server unable to Serve() also future invocations of Serve().

Now what happens if I want to re-start the same server after a
shutdown?

That's why it isn't done like that. If you by Shutdown() put also a non-running Server in disabled state, then you would have to invent some semantics of handling cases like:

Shutdown()
Shutdown()
Serve()
Serve()

Do you throw away the second Shutdown() event? If you do - then what's the difference? ... If you don't then will second Serve() also exit?

To me it's much simpler to just regard the Shutdown() as an async message with datagram semantics. You can always know if it was obeyed by waiting for Serve() to exit ... or use the ShutdownOK() bool function.

Since the server remembers the shutdown, then the newly
started server will immediately stop.

Yes... but what about multiple Shutdown() invokations?

If the server somehow "clears"
the shutdown, just before terminating, then there is again a small
window of time, before the server returns, when the shutdown will be
inadvertently set for the *next* server run.

Ah... no...
There's not "next" Server. You only have one object. Shutdown() asks to stop any running state it might have. If the same object is restarted, you still want it to stop by calling Shutdown().

In effect, you need a third method (something like: "Reset()"). Which
must be called after the server shuts-down and before it can be
re-started. And of-course this has to be documented so that user can
understand these subtle issues...

There is a 3rd method ... (namely Wait() ) ... but it's not used for that.
It's used for Servers which can be restarted before they are completely cleaned up. ... like in the provided http.Server extension where the old connectionManager can keep on shutting down HTTP Keepalive connections while the server object is restarted.

Alternatively, you can make it impossible for the same server
instance to be started, once it has been shutdown... Which is not
something I like very much.

The interface only defined Serve() and Shutdown() semantics (and Listen() and Wait() )
The docs does say that:
------------------------------
"Server is an server started by the master MultiServer, by calling Serve() which is expected to block
until the server is signaled to stop by an invocation of Shutdown()
Calling Shutdown() should start the shutdown process and return immediately, causing Serve() to exit. If the server implements Wait() its Serve() method can exit asynchronously before shutdown is fully completed. The master server will call Wait() before any restart of the server.

If Serve() returns no error, the master server regards the server as fully finished and ready to (maybe) be restarted by another invocation of the master MultiServer Serve() method."
-----------------------------

... it doesn't prevent you from implementing a broken server which will not obey that... However... that would be just as wrong as implementing a Serve() method which never exited.

On the other hand, using a context to handle termination avoids these
issues: A context is in-fact a termination token associated with a
single *RUN* of the server. This solves all these pesky problems.

Many shutdown schemes involves some channel to signal shutdown/cancel. In the http.Server mentioned above the channel is read by the Shutdown() ... it could also be closed by Shutdown() ... there are pros and cons. A Context use the last approach: Cancel() closes the channel (which is returned by Done(). The thing you get from Context is just a way to supply the channel used for signaling from the outside for every Serve(Context) call. So you know exactly which invocation of Serve() you are canceling.

In that regard you are right.

However... I can't really see that's the only valid semantics. Specifically ... if I want to tie the Shutdown() signal to an OS Signal (like SIGTERM)... then how do I know which Serve() invocation to cancel?

/Peter

--
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to