/
server.go
133 lines (107 loc) · 3.11 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package main
import (
"context"
"fmt"
"github.com/facebookgo/inject"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/woodsaj/go-server/cfg"
"github.com/woodsaj/go-server/registry"
"golang.org/x/sync/errgroup"
)
type CoreSrv struct {
context context.Context
shutdownFn context.CancelFunc
childRoutines *errgroup.Group
shutdownReason string
shutdownInProgress bool
}
func NewCoreSrv() *CoreSrv {
rootCtx, shutdownFn := context.WithCancel(context.Background())
childRoutines, childCtx := errgroup.WithContext(rootCtx)
return &CoreSrv{
context: childCtx,
shutdownFn: shutdownFn,
childRoutines: childRoutines,
}
}
func (srv *CoreSrv) Run() error {
serviceGraph := inject.Graph{}
config := cfg.New(viper.GetViper())
config.Watch()
// inject our config into each service
// This allows us to just simply provide direct configuration to each service if we dont
// want to use a configFile, EnvVars or cmdLine args
serviceGraph.Provide(&inject.Object{Value: config})
// inject our logger
serviceGraph.Provide(&inject.Object{Value: log.StandardLogger()})
services := registry.GetServices()
// Add all services to dependency graph
for _, service := range services {
service.Inject(&serviceGraph)
}
// Inject dependencies to services
if err := serviceGraph.Populate(); err != nil {
return fmt.Errorf("Failed to populate service dependency: %v", err)
}
// Init & start services
for _, service := range services {
if service.IsDisabled() {
continue
}
log.Info("Initializing " + service.Name)
if err := service.Instance.Init(); err != nil {
return fmt.Errorf("Service init failed: %v", err)
}
}
// Start background services
for _, svc := range services {
// variable needed for accessing loop variable in function callback
descriptor := svc
service, ok := descriptor.BackgroundService()
if !ok {
continue
}
if descriptor.IsDisabled() {
continue
}
srv.childRoutines.Go(func() error {
// Skip starting new service when shutting down
// Can happen when service stop/return during startup
if srv.shutdownInProgress {
return nil
}
err := service.Run(srv.context)
// If error is not canceled then the service crashed
if err != context.Canceled && err != nil {
log.Error("Stopped "+descriptor.Name, ". reason: ", err)
} else {
log.Info("Stopped "+descriptor.Name, ". reason: ", err)
}
// Mark that we are in shutdown mode
// So more services are not started
srv.shutdownInProgress = true
return err
})
}
return srv.childRoutines.Wait()
}
func (srv *CoreSrv) Shutdown(reason string) {
log.Info("Shutdown started. reason: ", reason)
srv.shutdownReason = reason
srv.shutdownInProgress = true
// call cancel func on root context
srv.shutdownFn()
// wait for child routines
srv.childRoutines.Wait()
}
func (srv *CoreSrv) Exit(reason error) int {
// default exit code is 1
code := 1
if (reason == nil || reason == context.Canceled) && srv.shutdownReason != "" {
reason = fmt.Errorf(srv.shutdownReason)
code = 0
}
log.Error("Server shutdown. reason: ", reason)
return code
}