-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
190 lines (155 loc) · 5.41 KB
/
main.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"syscall"
"time"
"github.com/leaf-ai/platform-services/internal/platform"
"github.com/leaf-ai/platform-services/internal/version"
"github.com/karlmutch/envflag"
"github.com/go-stack/stack"
"github.com/karlmutch/errors"
)
const serviceName = "restpoc"
var (
logger = platform.NewLogger(serviceName)
serverPort = flag.Int("port", 8000, "The TCP REST Server port number")
)
func usage() {
fmt.Fprintln(os.Stderr, path.Base(os.Args[0]))
fmt.Fprintln(os.Stderr, "usage: ", os.Args[0], "[arguments] experiment management service ", version.GitHash)
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Arguments:")
fmt.Fprintln(os.Stderr, "")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Environment Variables:")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "options can be read for environment variables by changing dashes '-' to underscores")
fmt.Fprintln(os.Stderr, "and using upper case letters.")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "To control log levels the LOGXI env variables can be used, these are documented at https://github.com/mgutz/logxi")
}
// Go runtime entry point for production builds. This function acts as an alias
// for the main.Main function. This allows testing and code coverage features of
// go to invoke the logic within the server main without skipping important
// runtime initialization steps. The coverage tools can then run this server as if it
// was a production binary.
//
// main will be called by the go runtime when the master is run in production mode
// avoiding this alias.
//
func main() {
quitC := make(chan struct{})
defer close(quitC)
// This is the one check that does not get tested when the server is under test
//
if _, err := platform.NewExclusive(serviceName, quitC); err != nil {
logger.Error(fmt.Sprintf("An instance of this process is already running %s", err.Error()))
os.Exit(-1)
}
Main()
}
// Production style main that will invoke the server as a go routine to allow
// a very simple supervisor and a test wrapper to coexist in terms of our logic.
//
// When using test mode 'go test ...' this function will not, normally, be run and
// instead the EntryPoint function will be called avoiding some initialization
// logic that is not applicable when testing. There is one exception to this
// and that is when the go unit test framework is linked to the master binary,
// using a TestRunMain build flag which allows a binary with coverage
// instrumentation to be compiled with only a single unit test which is,
// infact an alias to this main.
//
func Main() {
fmt.Printf("%s built against commit id %s\n", os.Args[0], version.GitHash)
flag.Usage = usage
// Use the go options parser to load command line options that have been set, and look
// for these options inside the env variable table
//
envflag.Parse()
doneC := make(chan struct{})
quitC := make(chan struct{})
if errs := EntryPoint(quitC, doneC); len(errs) != 0 {
for _, err := range errs {
logger.Error(err.Error())
}
os.Exit(-1)
}
// After starting the application message handling loops
// wait until the system has shutdown
//
select {
case <-quitC:
}
// Allow the quitC to be sent across the server for a short period of time before exiting
time.Sleep(time.Second)
}
// EntryPoint enables both test and standard production infrastructure to
// invoke this server.
//
// quitC can be used by the invoking functions to stop the processing
// inside the server and exit from the EntryPoint function
//
// doneC is used by the EntryPoint function to indicate when it has terminated
// its processing
//
func EntryPoint(quitC chan struct{}, doneC chan struct{}) (errs []errors.Error) {
defer close(doneC)
errs = []errors.Error{}
// Supplying the context allows the client to pubsub to cancel the
// blocking receive inside the run
ctx, cancel := context.WithCancel(context.Background())
// Setup a channel to allow a CTRL-C to terminate all processing. When the CTRL-C
// occurs we cancel the background msg pump processing pubsub mesages from
// google, and this will also cause the main thread to unblock and return
//
stopC := make(chan os.Signal)
go func() {
defer cancel()
select {
case <-quitC:
return
case <-stopC:
logger.Warn(errors.New("CTRL-C interrupted").With("stack", stack.Trace().TrimRuntime()).Error())
close(quitC)
return
}
}()
signal.Notify(stopC, os.Interrupt, syscall.SIGTERM)
msg := fmt.Sprintf("git hash version %s", version.GitHash)
logger.Info(msg)
handler, errs := Router(errs)
srv := &http.Server{
Addr: fmt.Sprintf(":%d", *serverPort),
Handler: handler,
}
if srv.Handler == nil {
return errs
}
// Now check for any fatal errors before allowing the system to continue. This allows
// all errors that could have ocuured as a result of incorrect options to be flushed
// out rather than having a frustrating single failure at a time loop for users
// to fix things
//
if len(errs) != 0 {
return errs
}
go func() {
if err := srv.ListenAndServe(); err != nil {
logger.Warn(errors.Wrap(err).With("stack", stack.Trace().TrimRuntime()).Error())
}
close(quitC)
}()
// Start a dummy service for now. Normally this would be the production main processing loop,
// or a collection of independently processing components
go func(ctx context.Context) {
<-ctx.Done()
}(ctx)
return nil
}