Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

Support graceful shutdown and hot-deploying #57

Merged
merged 6 commits into from
Mar 1, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: go
go:
- 1.7.4
- 1.8rc1
- 1.8
- tip

os:
Expand All @@ -15,4 +14,4 @@ install:
script:
- make bundle
- make check


2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ There are two way to install Gaurun; using a precompiled binary or install from

To install a precompiled binary, download the appropriate zip package for your OS and architecture from [here](https://github.com/mercari/gaurun/releases). Once the zip is downloaded, unzip it and place the binary where you want to use (if you want to access it from the command-line, make sure to put it on `$PATH`).

To compile from source, you need Go1.7.3 or later (including `$GOPATH` setup) and [glide](https://github.com/Masterminds/glide) for dependency management. After setup, then clone the source code by running the following command,
To compile from source, you need Go1.8 or later (including `$GOPATH` setup) and [glide](https://github.com/Masterminds/glide) for dependency management. After setup, then clone the source code by running the following command,

```bash
$ mkdir -p $GOPATH/src/github.com/mercari
Expand Down
47 changes: 42 additions & 5 deletions cmd/gaurun/gaurun.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package main

import (
"context"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/mercari/gaurun/gaurun"
)
Expand Down Expand Up @@ -85,8 +88,8 @@ func main() {
}
}

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGHUP)
sigHUPChan := make(chan os.Signal, 1)
signal.Notify(sigHUPChan, syscall.SIGHUP)

sighupHandler := func() {
if err := accessLogReopener.Reopen(); err != nil {
Expand All @@ -97,16 +100,50 @@ func main() {
}
}

go signalHandler(sigChan, sighupHandler)
go signalHandler(sigHUPChan, sighupHandler)

if err := gaurun.InitHttpClient(); err != nil {
gaurun.LogSetupFatal(fmt.Errorf("failed to init http client"))
}
gaurun.InitStat()
gaurun.StartPushWorkers(gaurun.ConfGaurun.Core.WorkerNum, gaurun.ConfGaurun.Core.QueueNum)

gaurun.RegisterHTTPHandlers()
gaurun.RunHTTPServer()
mux := http.NewServeMux()
gaurun.RegisterHandlers(mux)

server := &http.Server{
Handler: mux,
}
go func() {
gaurun.LogError.Info("start server")
if err := gaurun.RunServer(server, &gaurun.ConfGaurun); err != nil {
gaurun.LogError.Info(fmt.Sprintf("failed to serve: %s", err))
}
}()

// Graceful shutdown (kicked by TERM ).
//
// First, it shutdowns server and stops accepting new requests.
// Then wait until all remaining queues in buffer are flushed.
sigTERMChan := make(chan os.Signal, 1)
signal.Notify(sigTERMChan, syscall.SIGTERM)

<-sigTERMChan
gaurun.LogError.Info("shutdown server")
timeout := time.Duration(conf.Core.ShutdownTimeout) * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
gaurun.LogError.Error(fmt.Sprintf("failed to shutdown server: %v", err))
}

gaurun.LogError.Info("wait until queue is empty")
flushWaitInterval := 1 * time.Second
for len(gaurun.QueueNotification) > 0 {
time.Sleep(flushWaitInterval)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you improve this mechanism by channel-blocking? This implementation intend to wait 1sec in most cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can not get comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add global wait group !

}

gaurun.LogError.Info("successfully shutdown")
}

func signalHandler(ch <-chan os.Signal, sighupFn func()) {
Expand Down
2 changes: 2 additions & 0 deletions gaurun/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type SectionCore struct {
QueueNum int64 `toml:"queues"`
NotificationMax int64 `toml:"notification_max"`
PusherMax int64 `toml:"pusher_max"`
ShutdownTimeout int64 `toml:"shutdown_timeout"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document this in CONFIGURATION.md.

Copy link
Contributor

@cubicdaiya cubicdaiya Feb 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And add example config to conf/gaurun.toml.

}

type SectionAndroid struct {
Expand Down Expand Up @@ -64,6 +65,7 @@ func BuildDefaultConf() ConfToml {
conf.Core.QueueNum = 8192
conf.Core.NotificationMax = 100
conf.Core.PusherMax = 0
conf.Core.ShutdownTimeout = 10
// Android
conf.Android.ApiKey = ""
conf.Android.Enabled = true
Expand Down
76 changes: 56 additions & 20 deletions gaurun/server.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,83 @@
package gaurun

import (
"log"
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"

statsGo "github.com/fukata/golang-stats-api-handler"
"github.com/lestrrat/go-server-starter/listener"
)

func RegisterHTTPHandlers() {
http.HandleFunc("/push", PushNotificationHandler)
http.HandleFunc("/stat/app", StatsHandler)
http.HandleFunc("/config/app", ConfigHandler)
func RegisterHandlers(mux *http.ServeMux) {
mux.HandleFunc("/push", PushNotificationHandler)
mux.HandleFunc("/stat/app", StatsHandler)
mux.HandleFunc("/config/app", ConfigHandler)
mux.HandleFunc("/config/pushers", ConfigPushersHandler)

statsGo.PrettyPrintEnabled()
http.HandleFunc("/stat/go", statsGo.Handler)
http.HandleFunc("/config/pushers", ConfigPushersHandler)
mux.HandleFunc("/stat/go", statsGo.Handler)
}

func RunHTTPServer() {
// Listen TCP Port
if _, err := strconv.Atoi(ConfGaurun.Core.Port); err == nil {
http.ListenAndServe(":"+ConfGaurun.Core.Port, nil)
// getListener returns a listener.
func getListener(conf *ConfToml) (net.Listener, error) {
// By default, it starts to listen a listener provided
// by `go-server-starter`. If not, then check port defined
// in configuration file.
listeners, err := listener.ListenAll()
if err != nil && err != listener.ErrNoListeningTarget {
return nil, err
}

if len(listeners) > 0 {
return listeners[0], nil
}

// If port is empty, nothing to listen so returns error.
port := conf.Core.Port
if len(port) == 0 {
return nil, fmt.Errorf("no port to listen")
}

// Try to listen as TCP port, first
if _, err := strconv.Atoi(port); err == nil {
l, err := net.Listen("tcp", ":"+port)
if err != nil {
return nil, err
}
return l, nil
}

// Listen UNIX Socket
if strings.HasPrefix(ConfGaurun.Core.Port, "unix:/") {
sockPath := ConfGaurun.Core.Port[5:]
// Try to listen as UNIX socket.
if strings.HasPrefix(port, "unix:/") {
sockPath := port[5:]

fi, err := os.Lstat(sockPath)
if err == nil && (fi.Mode()&os.ModeSocket) == os.ModeSocket {
err := os.Remove(sockPath)
if err != nil {
log.Fatal("failed to remove " + sockPath)
if err := os.Remove(sockPath); err != nil {
return nil, fmt.Errorf("failed to remove socket path: %s", err)
}
}

l, err := net.Listen("unix", sockPath)
if err != nil {
log.Fatal("failed to listen: " + sockPath)
return nil, fmt.Errorf("failed to listen unix socket: %s", err)
}
http.Serve(l, nil)

return l, nil
}

return nil, fmt.Errorf("invalid port %s (it must be number or path start with 'unix:/')", port)
}

func RunServer(server *http.Server, conf *ConfToml) error {
l, err := getListener(conf)
if err != nil {
return err
}

log.Fatal("port parameter is invalid: " + ConfGaurun.Core.Port)
return server.Serve(l)
}
8 changes: 6 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ import:
- package: github.com/stretchr/testify
- package: github.com/uber-go/zap
- package: github.com/client9/reopen
- package: github.com/lestrrat/go-server-starter