1
0
mirror of https://github.com/kataras/iris.git synced 2026-02-10 12:46:17 +00:00

don't fire ErrServerClosed on manually interrupt signals (CTRL/CMD+C)

Former-commit-id: 673c84dd13bb99c0926aa1b4a6b4eff9745403d8
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-04-28 05:22:58 +03:00
parent 27ca1c93f5
commit b657c5e6af
44 changed files with 101 additions and 110 deletions

View File

@@ -190,6 +190,8 @@ Other Improvements:
![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png) ![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png)
- Server will not return neither log the `ErrServerClosed` if `app.Shutdown` was called manually via interrupt signal(CTRL/CMD+C), note that if the server closed by any other reason the error will be fired as previously (unless `iris.WithoutServerError(iris.ErrServerClosed)`).
- Finally, Log level's and Route debug information colorization is respected across outputs. Previously if the application used more than one output destination (e.g. a file through `app.Logger().AddOutput`) the color support was automatically disabled from all, including the terminal one, this problem is fixed now. Developers can now see colors in their terminals while log files are kept with clear text. - Finally, Log level's and Route debug information colorization is respected across outputs. Previously if the application used more than one output destination (e.g. a file through `app.Logger().AddOutput`) the color support was automatically disabled from all, including the terminal one, this problem is fixed now. Developers can now see colors in their terminals while log files are kept with clear text.
- New `iris.WithLowercaseRouting` option which forces all routes' paths to be lowercase and converts request paths to their lowercase for matching. - New `iris.WithLowercaseRouting` option which forces all routes' paths to be lowercase and converts request paths to their lowercase for matching.

View File

@@ -4,5 +4,5 @@ go 1.13
require ( require (
github.com/betacraft/yaag v1.0.1-0.20191027021412-565f65e36090 github.com/betacraft/yaag v1.0.1-0.20191027021412-565f65e36090
github.com/kataras/iris/v12 v12.1.5 github.com/kataras/iris/v12 v12.1.8
) )

View File

@@ -38,5 +38,5 @@ func main() {
// http://localhost:8080 // http://localhost:8080
// http://localhost:8080/ping // http://localhost:8080/ping
// http://localhost:8080/hello // http://localhost:8080/hello
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -50,8 +50,6 @@ func main() {
app.Run( app.Run(
// Start the web server at localhost:8080 // Start the web server at localhost:8080
iris.Addr("localhost:8080"), iris.Addr("localhost:8080"),
// skip err server closed when CTRL/CMD+C pressed:
iris.WithoutServerError(iris.ErrServerClosed),
// enables faster json serialization and more: // enables faster json serialization and more:
iris.WithOptimizations, iris.WithOptimizations,
) )

View File

@@ -32,8 +32,5 @@ func main() {
// Path: http://localhost:8080 // Path: http://localhost:8080
app.Get("/", indexHandler) app.Get("/", indexHandler)
app.Run( app.Listen(":8080")
iris.Addr(":8080"),
iris.WithoutServerError(iris.ErrServerClosed),
)
} }

View File

@@ -235,7 +235,7 @@ func main() {
app := iris.New() app := iris.New()
iris.RegisterOnInterrupt(func() { iris.RegisterOnInterrupt(func() {
timeout := 5 * time.Second timeout := 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
// close all hosts // close all hosts

View File

@@ -33,7 +33,7 @@ func main() {
case <-ch: case <-ch:
println("shutdown...") println("shutdown...")
timeout := 5 * time.Second timeout := 10 * time.Second
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout) ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
defer cancel() defer cancel()
app.Shutdown(ctx) app.Shutdown(ctx)

View File

@@ -18,7 +18,7 @@ func main() {
app := iris.New() app := iris.New()
iris.RegisterOnInterrupt(func() { iris.RegisterOnInterrupt(func() {
timeout := 5 * time.Second timeout := 10 * time.Second
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout) ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
defer cancel() defer cancel()
// close all hosts // close all hosts

View File

@@ -17,7 +17,8 @@ func main() {
} }
// same as: // same as:
// err := app.Listen(":8080") // err := app.Listen(":8080")
// if err != nil && (err != iris.ErrServerClosed || err.Error() != iris.ErrServerClosed.Error()) { // import "errors"
// if errors.Is(err, iris.ErrServerClosed) {
// [...] // [...]
// } // }
} }

View File

@@ -60,9 +60,8 @@ func TestListenAddrWithoutServerErr(t *testing.T) {
app.Shutdown(ctx) app.Shutdown(ctx)
}() }()
// we disable the ErrServerClosed, so the error should be nil when server is closed by `app.Shutdown`. // we disable the ErrServerClosed, so the error should be nil when server is closed by `app.Shutdown`
// or by an external issue.
// so in this case the iris/http.ErrServerClosed should be NOT logged and NOT return.
err := app.Listen(":9827", iris.WithoutServerError(iris.ErrServerClosed)) err := app.Listen(":9827", iris.WithoutServerError(iris.ErrServerClosed))
if err != nil { if err != nil {
t.Fatalf("expecting err to be nil but got: %v", err) t.Fatalf("expecting err to be nil but got: %v", err)

View File

@@ -11,14 +11,14 @@ func main() {
app := iris.New() app := iris.New()
app.Get("/", func(ctx iris.Context) { app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>Hello, try to refresh the page after ~10 secs</h1>") ctx.HTML("<h1>Hello, try to refresh the page after ~5 secs</h1>")
}) })
app.Logger().Info("Wait 10 seconds and check your terminal again") app.Logger().Info("Wait 5 seconds and check your terminal again")
// simulate a shutdown action here... // simulate a shutdown action here...
go func() { go func() {
<-time.After(10 * time.Second) <-time.After(5 * time.Second)
timeout := 5 * time.Second timeout := 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
// close all hosts, this will notify the callback we had register // close all hosts, this will notify the callback we had register
@@ -36,21 +36,24 @@ func main() {
// wait 10 seconds and check your terminal. // wait 10 seconds and check your terminal.
app.Run(iris.Addr(":8080", configureHost), iris.WithoutServerError(iris.ErrServerClosed)) app.Run(iris.Addr(":8080", configureHost), iris.WithoutServerError(iris.ErrServerClosed))
/* time.Sleep(500 * time.Millisecond) // give time to the separate go routine(`onServerShutdown`) to finish.
Or for simple cases you can just use the:
iris.RegisterOnInterrupt for global catch of the CTRL/CMD+C and OS events. /* See
Look at the "graceful-shutdown" example for more. iris.RegisterOnInterrupt(callback) for global catch of the CTRL/CMD+C and OS events.
Look at the "graceful-shutdown" example for more.
*/ */
} }
func onServerShutdown() {
println("server is closed")
}
func configureHost(su *iris.Supervisor) { func configureHost(su *iris.Supervisor) {
// here we have full access to the host that will be created // here we have full access to the host that will be created
// inside the `app.Run` function or `NewHost`. // inside the `app.Run` function or `NewHost`.
// //
// we're registering a shutdown "event" callback here: // we're registering a shutdown "event" callback here:
su.RegisterOnShutdown(func() { su.RegisterOnShutdown(onServerShutdown)
println("server is closed")
})
// su.RegisterOnError // su.RegisterOnError
// su.RegisterOnServe // su.RegisterOnServe
} }

View File

@@ -24,5 +24,5 @@ func main() {
// http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008 // http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008
// http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks // http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -19,7 +19,7 @@ func main() {
// //
// The response should be: // The response should be:
// Received: main.config{Addr:"localhost:8080", ServerName:"Iris"} // Received: main.config{Addr:"localhost:8080", ServerName:"Iris"}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) app.Listen(":8080", iris.WithOptimizations)
} }
func newApp() *iris.Application { func newApp() *iris.Application {

View File

@@ -19,7 +19,7 @@ func main() {
// //
// The response should be: // The response should be:
// Received: main.config{Addr:"localhost:8080", ServerName:"Iris"} // Received: main.config{Addr:"localhost:8080", ServerName:"Iris"}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) app.Listen(":8080", iris.WithOptimizations)
} }
func newApp() *iris.Application { func newApp() *iris.Application {

View File

@@ -60,5 +60,5 @@ func main() {
// //
// The response should be: // The response should be:
// Received: main.Company{Name:"iris-Go", City:"New York", Other:"Something here"} // Received: main.Company{Name:"iris-Go", City:"New York", Other:"Something here"}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) app.Listen(":8080", iris.WithOptimizations)
} }

View File

@@ -20,7 +20,7 @@ func main() {
// //
// The response should be: // The response should be:
// Received: main.person{XMLName:xml.Name{Space:"", Local:"person"}, Name:"Winston Churchill", Age:90, Description:"Description of this person, the body of this inner element."} // Received: main.person{XMLName:xml.Name{Space:"", Local:"person"}, Name:"Winston Churchill", Age:90, Description:"Description of this person, the body of this inner element."}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) app.Listen(":8080", iris.WithOptimizations)
} }
func newApp() *iris.Application { func newApp() *iris.Application {

View File

@@ -61,5 +61,5 @@ func main() {
// http://localhost:8080/2 // http://localhost:8080/2
// http://lcoalhost:8080/notfoundhere // http://lcoalhost:8080/notfoundhere
// see the output on the console. // see the output on the console.
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -82,7 +82,7 @@ func main() {
// http://localhost:8080/1 // http://localhost:8080/1
// http://localhost:8080/2 // http://localhost:8080/2
// http://lcoalhost:8080/notfoundhere // http://lcoalhost:8080/notfoundhere
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }
var excludeExtensions = [...]string{ var excludeExtensions = [...]string{

View File

@@ -35,7 +35,7 @@ func main() {
// http://localhost:8080/1 // http://localhost:8080/1
// http://localhost:8080/2 // http://localhost:8080/2
// http://lcoalhost:8080/notfoundhere // http://lcoalhost:8080/notfoundhere
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }
// get a filename based on the date, file logs works that way the most times // get a filename based on the date, file logs works that way the most times

View File

@@ -18,5 +18,5 @@ func main() {
app := newApp() app := newApp()
// http://localhost:8080 // http://localhost:8080
// http://localhost:8080/yourname // http://localhost:8080/yourname
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -44,7 +44,7 @@ func main() {
}) })
}() // ... }() // ...
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }
/* For a golang SSE client you can look at: https://github.com/r3labs/sse#example-client */ /* For a golang SSE client you can look at: https://github.com/r3labs/sse#example-client */

View File

@@ -187,5 +187,5 @@ func main() {
// http://localhost:8080 // http://localhost:8080
// http://localhost:8080/events // http://localhost:8080/events
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -113,5 +113,5 @@ func main() {
// //
// `iris.WithoutServerError` is an optional configurator, // `iris.WithoutServerError` is an optional configurator,
// if passed to the `Run` then it will not print its passed error as an actual server error. // if passed to the `Run` then it will not print its passed error as an actual server error.
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) app.Listen(":8080", iris.WithOptimizations)
} }

View File

@@ -42,7 +42,7 @@ func main() {
// Navigate to http://localhost:8080/ping // Navigate to http://localhost:8080/ping
// and open the ./logs{TODAY}.txt file. // and open the ./logs{TODAY}.txt file.
if err := app.Listen(":8080", iris.WithoutBanner, iris.WithoutServerError(iris.ErrServerClosed)); err != nil { if err := app.Listen(":8080", iris.WithoutBanner); err != nil {
app.Logger().Warn("Shutdown with error: " + err.Error()) app.Logger().Warn("Shutdown with error: " + err.Error())
} }
} }

View File

@@ -35,7 +35,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
resp, err := client.Post("https://localhost/hello", "application/json", buf) resp, err := client.Post("https://localhost/helloworld.Greeter/SayHello", "application/json", buf)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@@ -23,7 +23,7 @@ func main() {
app.Logger().SetLevel("debug") app.Logger().SetLevel("debug")
// The Iris server should ran under TLS (it's a gRPC requirement). // The Iris server should ran under TLS (it's a gRPC requirement).
// POST: https://localhost:443/helloworld.greeter/sayhello // POST: https://localhost:443/helloworld.Greeter/SayHello
// with request data: {"name": "John"} // with request data: {"name": "John"}
// and expected output: {"message": "Hello John"} // and expected output: {"message": "Hello John"}
app.Run(iris.TLS(":443", "server.crt", "server.key")) app.Run(iris.TLS(":443", "server.crt", "server.key"))
@@ -32,7 +32,6 @@ func main() {
func newApp() *iris.Application { func newApp() *iris.Application {
app := iris.New() app := iris.New()
// app.Configure(iris.WithLowercaseRouting) // OPTIONAL. // app.Configure(iris.WithLowercaseRouting) // OPTIONAL.
app.Logger().SetLevel("debug")
app.Get("/", func(ctx iris.Context) { app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>Index Page</h1>") ctx.HTML("<h1>Index Page</h1>")
@@ -55,7 +54,9 @@ func newApp() *iris.Application {
return app return app
} }
type myController struct{} type myController struct {
// Ctx iris.Context
}
// SayHello implements helloworld.GreeterServer. // SayHello implements helloworld.GreeterServer.
func (c *myController) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { func (c *myController) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {

View File

@@ -76,12 +76,8 @@ func main() {
// http://localhost:8080/user/me // http://localhost:8080/user/me
// http://localhost:8080/user/logout // http://localhost:8080/user/logout
// basic auth: "admin", "password", see "./middleware/basicauth.go" source file. // basic auth: "admin", "password", see "./middleware/basicauth.go" source file.
app.Run(
// Starts the web server at localhost:8080 // Starts the web server at localhost:8080
iris.Addr("localhost:8080"), // Enables faster json serialization and more.
// Ignores err server closed log when CTRL/CMD+C pressed. app.Listen(":8080", iris.WithOptimizations)
iris.WithoutServerError(iris.ErrServerClosed),
// Enables faster json serialization and more.
iris.WithOptimizations,
)
} }

View File

@@ -30,14 +30,7 @@ func main() {
// http://localhost:8080/hello/iris // http://localhost:8080/hello/iris
// http://localhost:8080/movies // http://localhost:8080/movies
// http://localhost:8080/movies/1 // http://localhost:8080/movies/1
app.Run( app.Listen(":8080", iris.WithOptimizations)
// Start the web server at localhost:8080
iris.Addr("localhost:8080"),
// skip err server closed when CTRL/CMD+C pressed:
iris.WithoutServerError(iris.ErrServerClosed),
// enables faster json serialization and more:
iris.WithOptimizations,
)
} }
// note the mvc.Application, it's not iris.Application. // note the mvc.Application, it's not iris.Application.

View File

@@ -165,7 +165,8 @@ func main() {
"data": user.Serializer(), "data": user.Serializer(),
}) })
}) })
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
} }
type patchParam struct { type patchParam struct {

View File

@@ -70,5 +70,5 @@ func main() {
// http://localhost:8080/insert // http://localhost:8080/insert
// http://localhost:8080/get // http://localhost:8080/get
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -98,5 +98,5 @@ func main() {
myCustomRouter := new(customRouter) myCustomRouter := new(customRouter)
app.BuildRouter(app.ContextPool, myCustomRouter, app.APIBuilder, true) app.BuildRouter(app.ContextPool, myCustomRouter, app.APIBuilder, true)
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -38,7 +38,7 @@ func main() {
// this will handle only GET "/other2/static" // this will handle only GET "/other2/static"
app.Get("/other2/static2", staticPathOther2) app.Get("/other2/static2", staticPathOther2)
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }
func h(ctx iris.Context) { func h(ctx iris.Context) {

View File

@@ -104,5 +104,5 @@ func main() {
} }
}) })
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -105,5 +105,5 @@ func main() {
} }
}) })
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -77,7 +77,7 @@ func main() {
// POST, GET: http://localhost:8080/api/v1/topics // POST, GET: http://localhost:8080/api/v1/topics
// POST : http://localhost:8080/apiv1/topics/{topic}/produce?key=my-key // POST : http://localhost:8080/apiv1/topics/{topic}/produce?key=my-key
// GET : http://localhost:8080/apiv1/topics/{topic}/consume?partition=0&offset=0 (these url query parameters are optional) // GET : http://localhost:8080/apiv1/topics/{topic}/consume?partition=0&offset=0 (these url query parameters are optional)
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }
// simple use-case, you can use templates and views obviously, see the "_examples/views" examples. // simple use-case, you can use templates and views obviously, see the "_examples/views" examples.

View File

@@ -116,5 +116,5 @@ func main() {
// serves the npm browser websocket client usage example. // serves the npm browser websocket client usage example.
app.HandleDir("/browserify", "./browserify") app.HandleDir("/browserify", "./browserify")
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) app.Listen(":8080")
} }

View File

@@ -3,6 +3,6 @@ module github.com/kataras/iris/_examples/websocket/socketio
go 1.13 go 1.13
require ( require (
github.com/googollee/go-socket.io v1.4.3-0.20191109153049-7451e2f8c2e0 // indirect github.com/googollee/go-socket.io v1.4.3-0.20191109153049-7451e2f8c2e0
github.com/kataras/iris/v12 v12.1.5 github.com/kataras/iris/v12 v12.1.8
) )

View File

@@ -47,10 +47,8 @@ func main() {
app.HandleMany("GET POST", "/socket.io/{any:path}", iris.FromStd(server)) app.HandleMany("GET POST", "/socket.io/{any:path}", iris.FromStd(server))
app.HandleDir("/", "./asset") app.HandleDir("/", "./asset")
app.Listen(":8000",
iris.WithoutPathCorrection, app.Listen(":8000", iris.WithoutPathCorrection)
iris.WithoutServerError(iris.ErrServerClosed),
)
} }
/* /*

View File

@@ -185,11 +185,8 @@ var WithGlobalConfiguration = func(app *Application) {
app.Configure(WithConfiguration(YAML(globalConfigurationKeyword))) app.Configure(WithConfiguration(YAML(globalConfigurationKeyword)))
} }
// variables for configurators don't need any receivers, functions
// for them that need (helps code editors to recognise as variables without parenthesis completion).
// WithoutServerError will cause to ignore the matched "errors" // WithoutServerError will cause to ignore the matched "errors"
// from the main application's `Run` function. // from the main application's `Run/Listen` function.
// //
// Usage: // Usage:
// err := app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed)) // err := app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))

View File

@@ -1,6 +1,7 @@
package context package context
import ( import (
stdContext "context"
"io" "io"
"net/http" "net/http"
@@ -41,6 +42,10 @@ type Application interface {
// It is ready to use after Build state. // It is ready to use after Build state.
ServeHTTP(w http.ResponseWriter, r *http.Request) ServeHTTP(w http.ResponseWriter, r *http.Request)
// Shutdown gracefully terminates all the application's server hosts and any tunnels.
// Returns an error on the first failure, otherwise nil.
Shutdown(ctx stdContext.Context) error
// GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil. // GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil.
// One note: "routeName" should be case-sensitive. Used by the context to get the current route. // One note: "routeName" should be case-sensitive. Used by the context to get the current route.
// It returns an interface instead to reduce wrong usage and to keep the decoupled design between // It returns an interface instead to reduce wrong usage and to keep the decoupled design between

View File

@@ -27,11 +27,12 @@ type Configurator func(su *Supervisor)
// //
// Interfaces are separated to return relative functionality to them. // Interfaces are separated to return relative functionality to them.
type Supervisor struct { type Supervisor struct {
Server *http.Server Server *http.Server
closedManually int32 // future use, accessed atomically (non-zero means we've called the Shutdown) closedManually uint32 // future use, accessed atomically (non-zero means we've called the Shutdown)
manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin. closedByInterruptHandler uint32 // non-zero means that the end-developer interrupted it by-purpose.
shouldWait int32 // non-zero means that the host should wait for unblocking manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin.
unblockChan chan struct{} shouldWait int32 // non-zero means that the host should wait for unblocking
unblockChan chan struct{}
mu sync.Mutex mu sync.Mutex
@@ -39,14 +40,11 @@ type Supervisor struct {
// IgnoreErrors should contains the errors that should be ignored // IgnoreErrors should contains the errors that should be ignored
// on both serve functions return statements and error handlers. // on both serve functions return statements and error handlers.
// //
// i.e: http.ErrServerClosed.Error().
//
// Note that this will match the string value instead of the equality of the type's variables. // Note that this will match the string value instead of the equality of the type's variables.
// //
// Defaults to empty. // Defaults to empty.
IgnoredErrors []string IgnoredErrors []string
onErr []func(error) onErr []func(error)
onShutdown []func()
} }
// New returns a new host supervisor // New returns a new host supervisor
@@ -143,6 +141,10 @@ func (su *Supervisor) validateErr(err error) error {
return nil return nil
} }
if errors.Is(err, http.ErrServerClosed) && atomic.LoadUint32(&su.closedByInterruptHandler) > 0 {
return nil
}
su.mu.Lock() su.mu.Lock()
defer su.mu.Unlock() defer su.mu.Unlock()
@@ -151,6 +153,7 @@ func (su *Supervisor) validateErr(err error) error {
return nil return nil
} }
} }
return err return err
} }
@@ -189,6 +192,8 @@ func (su *Supervisor) supervise(blockFunc func() error) error {
host := createTaskHost(su) host := createTaskHost(su)
su.notifyServe(host) su.notifyServe(host)
atomic.StoreUint32(&su.closedByInterruptHandler, 0)
atomic.StoreUint32(&su.closedManually, 0)
err := blockFunc() err := blockFunc()
su.notifyErr(err) su.notifyErr(err)
@@ -319,7 +324,7 @@ func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDi
// supervisor in order to close the "secondary redirect server" as well. // supervisor in order to close the "secondary redirect server" as well.
su.RegisterOnShutdown(func() { su.RegisterOnShutdown(func() {
// give it some time to close itself... // give it some time to close itself...
timeout := 5 * time.Second timeout := 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
srv2.Shutdown(ctx) srv2.Shutdown(ctx)
@@ -346,21 +351,10 @@ func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDi
// undergone NPN/ALPN protocol upgrade or that have been hijacked. // undergone NPN/ALPN protocol upgrade or that have been hijacked.
// This function should start protocol-specific graceful shutdown, // This function should start protocol-specific graceful shutdown,
// but should not wait for shutdown to complete. // but should not wait for shutdown to complete.
//
// Callbacks will run as separate go routines.
func (su *Supervisor) RegisterOnShutdown(cb func()) { func (su *Supervisor) RegisterOnShutdown(cb func()) {
// when go1.9: replace the following lines with su.Server.RegisterOnShutdown(f) su.Server.RegisterOnShutdown(cb)
su.mu.Lock()
su.onShutdown = append(su.onShutdown, cb)
su.mu.Unlock()
}
func (su *Supervisor) notifyShutdown() {
// when go1.9: remove the lines below
su.mu.Lock()
for _, f := range su.onShutdown {
go f()
}
su.mu.Unlock()
// end
} }
// Shutdown gracefully shuts down the server without interrupting any // Shutdown gracefully shuts down the server without interrupting any
@@ -375,7 +369,11 @@ func (su *Supervisor) notifyShutdown() {
// separately notify such long-lived connections of shutdown and wait // separately notify such long-lived connections of shutdown and wait
// for them to close, if desired. // for them to close, if desired.
func (su *Supervisor) Shutdown(ctx context.Context) error { func (su *Supervisor) Shutdown(ctx context.Context) error {
atomic.AddInt32(&su.closedManually, 1) // future-use atomic.StoreUint32(&su.closedManually, 1) // future-use
su.notifyShutdown()
return su.Server.Shutdown(ctx) return su.Server.Shutdown(ctx)
} }
func (su *Supervisor) shutdownOnInterrupt(ctx context.Context) {
atomic.StoreUint32(&su.closedByInterruptHandler, 1)
su.Shutdown(ctx)
}

View File

@@ -87,7 +87,7 @@ func ExampleSupervisor_RegisterOnServe() {
logger := log.New(os.Stdout, "Supervisor: ", 0) logger := log.New(os.Stdout, "Supervisor: ", 0)
mytask := myTestTask{ mytask := myTestTask{
restartEvery: 6 * time.Second, restartEvery: 3 * time.Second,
maxRestarts: 2, maxRestarts: 2,
logger: logger, logger: logger,
} }

View File

@@ -6,6 +6,7 @@ package host
// supervisor. // supervisor.
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -27,8 +28,9 @@ func WriteStartupLogOnServe(w io.Writer) func(TaskHost) {
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
interruptkey = "CMD" interruptkey = "CMD"
} }
_, _ = w.Write([]byte(fmt.Sprintf("Now listening on: %s\nApplication started. Press %s+C to shut down.\n",
listeningURI, interruptkey))) _, _ = fmt.Fprintf(w, "Now listening on: %s\nApplication started. Press %s+C to shut down.\n",
listeningURI, interruptkey)
} }
} }
@@ -38,7 +40,7 @@ func ShutdownOnInterrupt(su *Supervisor, shutdownTimeout time.Duration) func() {
return func() { return func() {
ctx, cancel := context.WithTimeout(context.TODO(), shutdownTimeout) ctx, cancel := context.WithTimeout(context.TODO(), shutdownTimeout)
defer cancel() defer cancel()
su.Shutdown(ctx) su.shutdownOnInterrupt(ctx)
su.RestoreFlow() su.RestoreFlow()
} }
} }
@@ -58,9 +60,9 @@ func (h TaskHost) Serve() error {
return err return err
} }
// if http.serverclosed ignroe the error, it will have this error // if http.serverclosed ignore the error, it will have this error
// from the previous close // from the previous close
if err := h.Supervisor.Server.Serve(l); err != http.ErrServerClosed { if err := h.Supervisor.Server.Serve(l); !errors.Is(err, http.ErrServerClosed) {
return err return err
} }
return nil return nil

View File

@@ -668,8 +668,8 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
} }
if !app.config.DisableInterruptHandler { if !app.config.DisableInterruptHandler {
// when CTRL+C/CMD+C pressed. // when CTRL/CMD+C pressed.
shutdownTimeout := 5 * time.Second shutdownTimeout := 10 * time.Second
host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout)) host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout))
// app.logger.Debugf("Host: register server shutdown on interrupt(CTRL+C/CMD+C)") // app.logger.Debugf("Host: register server shutdown on interrupt(CTRL+C/CMD+C)")
} }