blob: eca766af372e0c8e4b49f27ade509150c25de888 [file] [log] [blame]
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -08001package rt_test
2
3import (
4 "bufio"
5 "fmt"
6 "io"
7 "os"
8 "os/signal"
9 "sync"
10 "syscall"
11
Jiri Simsa764efb72014-12-25 20:57:03 -080012 "v.io/core/veyron2"
13 "v.io/core/veyron2/ipc"
14 "v.io/core/veyron2/rt"
15 "v.io/core/veyron2/vlog"
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080016
Jiri Simsa764efb72014-12-25 20:57:03 -080017 "v.io/core/veyron/lib/modules"
18 "v.io/core/veyron/lib/signals"
19 "v.io/core/veyron/profiles"
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080020)
21
22func init() {
23 modules.RegisterChild("simpleServerProgram", "", simpleServerProgram)
24 modules.RegisterChild("complexServerProgram", "", complexServerProgram)
25}
26
27type dummy struct{}
28
29func (*dummy) Echo(ipc.ServerContext) error { return nil }
30
31// makeServer sets up a simple dummy server.
Matt Rosencrantz0610a232014-12-04 10:26:39 -080032func makeServer(r veyron2.Runtime) ipc.Server {
33 server, err := r.NewServer()
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080034 if err != nil {
35 vlog.Fatalf("r.NewServer error: %s", err)
36 }
37 if _, err := server.Listen(profiles.LocalListenSpec); err != nil {
38 vlog.Fatalf("server.Listen error: %s", err)
39 }
40 if err := server.Serve("", &dummy{}, nil); err != nil {
41 vlog.Fatalf("server.Serve error: %s", err)
42 }
43 return server
44}
45
46// remoteCmdLoop listens on stdin and interprets commands sent over stdin (from
47// the parent process).
Matt Rosencrantz0610a232014-12-04 10:26:39 -080048func remoteCmdLoop(r veyron2.Runtime, stdin io.Reader) func() {
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080049 done := make(chan struct{})
50 go func() {
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080051 scanner := bufio.NewScanner(stdin)
52 for scanner.Scan() {
53 switch scanner.Text() {
54 case "stop":
Matt Rosencrantz0610a232014-12-04 10:26:39 -080055 r.AppCycle().Stop()
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080056 case "forcestop":
57 fmt.Println("straight exit")
Matt Rosencrantz0610a232014-12-04 10:26:39 -080058 r.AppCycle().ForceStop()
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080059 case "close":
Bogdan Capritae06d7152014-11-22 09:32:05 -080060 close(done)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080061 return
62 }
63 }
64 }()
65 return func() { <-done }
66}
67
68// complexServerProgram demonstrates the recommended way to write a more
69// complex server application (with several servers, a mix of interruptible
70// and blocking cleanup, and parallel and sequential cleanup execution).
71// For a more typical server, see simpleServerProgram.
72func complexServerProgram(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
73 // Initialize the runtime. This is boilerplate.
Matt Rosencrantz0610a232014-12-04 10:26:39 -080074 r, err := rt.New()
75 if err != nil {
76 vlog.Fatalf("Could not initialize runtime: %s", err)
77 }
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080078
79 // This is part of the test setup -- we need a way to accept
80 // commands from the parent process to simulate Stop and
81 // RemoteStop commands that would normally be issued from
82 // application code.
Matt Rosencrantz0610a232014-12-04 10:26:39 -080083 defer remoteCmdLoop(r, stdin)()
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080084
85 // r.Cleanup is optional, but it's a good idea to clean up, especially
86 // since it takes care of flushing the logs before exiting.
87 defer r.Cleanup()
88
89 // Create a couple servers, and start serving.
Matt Rosencrantz0610a232014-12-04 10:26:39 -080090 server1 := makeServer(r)
91 server2 := makeServer(r)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080092
93 // This is how to wait for a shutdown. In this example, a shutdown
94 // comes from a signal or a stop command.
95 var done sync.WaitGroup
96 done.Add(1)
97
98 // This is how to configure signal handling to allow clean shutdown.
99 sigChan := make(chan os.Signal, 2)
100 signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
101
102 // This is how to configure handling of stop commands to allow clean
103 // shutdown.
104 stopChan := make(chan string, 2)
105 r.AppCycle().WaitForStop(stopChan)
106
107 // Blocking is used to prevent the process from exiting upon receiving a
108 // second signal or stop command while critical cleanup code is
109 // executing.
110 var blocking sync.WaitGroup
111 blockingCh := make(chan struct{})
112
113 // This is how to wait for a signal or stop command and initiate the
114 // clean shutdown steps.
115 go func() {
116 // First signal received.
117 select {
118 case sig := <-sigChan:
119 // If the developer wants to take different actions
120 // depending on the type of signal, they can do it here.
121 fmt.Fprintln(stdout, "Received signal", sig)
122 case stop := <-stopChan:
123 fmt.Fprintln(stdout, "Stop", stop)
124 }
125 // This commences the cleanup stage.
126 done.Done()
127 // Wait for a second signal or stop command, and force an exit,
128 // but only once all blocking cleanup code (if any) has
129 // completed.
130 select {
131 case <-sigChan:
132 case <-stopChan:
133 }
134 <-blockingCh
Bogdan Capritae06d7152014-11-22 09:32:05 -0800135 os.Exit(signals.DoubleStopExitCode)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800136 }()
137
138 // This communicates to the parent test driver process in our unit test
139 // that this server is ready and waiting on signals or stop commands.
140 // It's purely an artifact of our test setup.
141 fmt.Fprintln(stdout, "Ready")
142
143 // Wait for shutdown.
144 done.Wait()
145
146 // Stop the servers. In this example we stop them in goroutines to
147 // parallelize the wait, but if there was a dependency between the
148 // servers, the developer can simply stop them sequentially.
149 var waitServerStop sync.WaitGroup
150 waitServerStop.Add(2)
151 go func() {
152 server1.Stop()
153 waitServerStop.Done()
154 }()
155 go func() {
156 server2.Stop()
157 waitServerStop.Done()
158 }()
159 waitServerStop.Wait()
160
161 // This is where all cleanup code should go. By placing it at the end,
162 // we make its purpose and order of execution clear.
163
164 // This is an example of how to mix parallel and sequential cleanup
165 // steps. Most real-world servers will likely be simpler, with either
166 // just sequential or just parallel cleanup stages.
167
168 // parallelCleanup is used to wait for all goroutines executing cleanup
169 // code in parallel to finish.
170 var parallelCleanup sync.WaitGroup
171
172 // Simulate four parallel cleanup steps, two blocking and two
173 // interruptible.
174 parallelCleanup.Add(1)
175 blocking.Add(1)
176 go func() {
177 fmt.Fprintln(stdout, "Parallel blocking cleanup1")
178 blocking.Done()
179 parallelCleanup.Done()
180 }()
181
182 parallelCleanup.Add(1)
183 blocking.Add(1)
184 go func() {
185 fmt.Fprintln(stdout, "Parallel blocking cleanup2")
186 blocking.Done()
187 parallelCleanup.Done()
188 }()
189
190 parallelCleanup.Add(1)
191 go func() {
192 fmt.Fprintln(stdout, "Parallel interruptible cleanup1")
193 parallelCleanup.Done()
194 }()
195
196 parallelCleanup.Add(1)
197 go func() {
198 fmt.Fprintln(stdout, "Parallel interruptible cleanup2")
199 parallelCleanup.Done()
200 }()
201
202 // Simulate two sequential cleanup steps, one blocking and one
203 // interruptible.
204 fmt.Fprintln(stdout, "Sequential blocking cleanup")
205 blocking.Wait()
206 close(blockingCh)
207
208 fmt.Fprintln(stdout, "Sequential interruptible cleanup")
209
210 parallelCleanup.Wait()
211 return nil
212}
213
214// simpleServerProgram demonstrates the recommended way to write a typical
215// simple server application (with one server and a clean shutdown triggered by
216// a signal or a stop command). For an example of something more involved, see
217// complexServerProgram.
218func simpleServerProgram(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
219 // Initialize the runtime. This is boilerplate.
Matt Rosencrantz0610a232014-12-04 10:26:39 -0800220 r, err := rt.New()
221 if err != nil {
222 vlog.Fatalf("Could not initialize runtime: %s", err)
223 }
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800224
225 // This is part of the test setup -- we need a way to accept
226 // commands from the parent process to simulate Stop and
227 // RemoteStop commands that would normally be issued from
228 // application code.
Matt Rosencrantz0610a232014-12-04 10:26:39 -0800229 defer remoteCmdLoop(r, stdin)()
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800230
231 // r.Cleanup is optional, but it's a good idea to clean up, especially
232 // since it takes care of flushing the logs before exiting.
233 //
234 // We use defer to ensure this is the last thing in the program (to
235 // avoid shutting down the runtime while it may still be in use), and to
236 // allow it to execute even if a panic occurs down the road.
237 defer r.Cleanup()
238
239 // Create a server, and start serving.
Matt Rosencrantz0610a232014-12-04 10:26:39 -0800240 server := makeServer(r)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800241
242 // This is how to wait for a shutdown. In this example, a shutdown
243 // comes from a signal or a stop command.
244 //
245 // Note, if the developer wants to exit immediately upon receiving a
246 // signal or stop command, they can skip this, in which case the default
247 // behavior is for the process to exit.
Matt Rosencrantzc7fecf12014-11-27 19:58:43 -0800248 waiter := signals.ShutdownOnSignals(r)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800249
250 // This communicates to the parent test driver process in our unit test
251 // that this server is ready and waiting on signals or stop commands.
252 // It's purely an artifact of our test setup.
253 fmt.Fprintln(stdout, "Ready")
254
255 // Use defer for anything that should still execute even if a panic
256 // occurs.
257 defer fmt.Fprintln(stdout, "Deferred cleanup")
258
259 // Wait for shutdown.
260 sig := <-waiter
261 // The developer could take different actions depending on the type of
262 // signal.
263 fmt.Fprintln(stdout, "Received signal", sig)
264
265 // Cleanup code starts here. Alternatively, these steps could be
266 // invoked through defer, but we list them here to make the order of
267 // operations obvious.
268
269 // Stop the server.
270 server.Stop()
271
272 // Note, this will not execute in cases of forced shutdown
273 // (e.g. SIGSTOP), when the process calls os.Exit (e.g. via log.Fatal),
274 // or when a panic occurs.
275 fmt.Fprintln(stdout, "Interruptible cleanup")
276
277 return nil
278}