blob: 8133615ede05798b95533973e6c4c51c56f8ce45 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -08005package rt_test
6
7import (
8 "bufio"
9 "fmt"
10 "io"
11 "os"
12 "os/signal"
13 "sync"
14 "syscall"
15
Jiri Simsa6ac95222015-02-23 16:11:49 -080016 "v.io/v23"
17 "v.io/v23/context"
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070018 "v.io/v23/rpc"
Jiri Simsaffceefa2015-02-28 11:03:34 -080019 "v.io/x/ref/lib/signals"
Suharsh Sivakumardcc11d72015-05-11 12:19:20 -070020 _ "v.io/x/ref/runtime/factories/generic"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070021 "v.io/x/ref/test"
22 "v.io/x/ref/test/modules"
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080023)
24
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080025type dummy struct{}
26
Todd Wang54feabe2015-04-15 23:38:26 -070027func (*dummy) Echo(*context.T, rpc.ServerCall) error { return nil }
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080028
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080029// remoteCmdLoop listens on stdin and interprets commands sent over stdin (from
30// the parent process).
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -080031func remoteCmdLoop(ctx *context.T, stdin io.Reader) func() {
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080032 done := make(chan struct{})
33 go func() {
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080034 scanner := bufio.NewScanner(stdin)
35 for scanner.Scan() {
36 switch scanner.Text() {
37 case "stop":
Cosmos Nicolaoue9c622d2015-07-10 11:09:42 -070038 v23.GetAppCycle(ctx).Stop(ctx)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080039 case "forcestop":
40 fmt.Println("straight exit")
Cosmos Nicolaoue9c622d2015-07-10 11:09:42 -070041 v23.GetAppCycle(ctx).ForceStop(ctx)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080042 case "close":
Bogdan Capritae06d7152014-11-22 09:32:05 -080043 close(done)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080044 return
45 }
46 }
47 }()
48 return func() { <-done }
49}
50
51// complexServerProgram demonstrates the recommended way to write a more
52// complex server application (with several servers, a mix of interruptible
53// and blocking cleanup, and parallel and sequential cleanup execution).
54// For a more typical server, see simpleServerProgram.
Todd Wang95873902015-05-22 14:21:30 -070055var complexServerProgram = modules.Register(func(env *modules.Env, args ...string) error {
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080056 // Initialize the runtime. This is boilerplate.
Todd Wang60052d82015-05-22 15:00:10 -070057 ctx, shutdown := test.V23Init()
Suharsh Sivakumard5049b72015-01-21 14:11:35 -080058 // shutdown is optional, but it's a good idea to clean up, especially
59 // since it takes care of flushing the logs before exiting.
60 defer shutdown()
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -080061
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080062 // This is part of the test setup -- we need a way to accept
63 // commands from the parent process to simulate Stop and
64 // RemoteStop commands that would normally be issued from
65 // application code.
Todd Wang95873902015-05-22 14:21:30 -070066 defer remoteCmdLoop(ctx, env.Stdin)()
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080067
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080068 // Create a couple servers, and start serving.
Matt Rosencrantz53ac5852015-09-04 15:14:54 -070069 ctx, server1, err := v23.WithNewServer(ctx, "", &dummy{}, nil)
Matt Rosencrantzbb6295d2015-06-19 15:13:58 -070070 if err != nil {
Cosmos Nicolaoue9c622d2015-07-10 11:09:42 -070071 ctx.Fatalf("r.NewServer error: %s", err)
Matt Rosencrantzbb6295d2015-06-19 15:13:58 -070072 }
Matt Rosencrantz53ac5852015-09-04 15:14:54 -070073 ctx, server2, err := v23.WithNewServer(ctx, "", &dummy{}, nil)
Matt Rosencrantzbb6295d2015-06-19 15:13:58 -070074 if err != nil {
Cosmos Nicolaoue9c622d2015-07-10 11:09:42 -070075 ctx.Fatalf("r.NewServer error: %s", err)
Matt Rosencrantzbb6295d2015-06-19 15:13:58 -070076 }
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080077
78 // This is how to wait for a shutdown. In this example, a shutdown
79 // comes from a signal or a stop command.
80 var done sync.WaitGroup
81 done.Add(1)
82
83 // This is how to configure signal handling to allow clean shutdown.
84 sigChan := make(chan os.Signal, 2)
85 signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
86
87 // This is how to configure handling of stop commands to allow clean
88 // shutdown.
89 stopChan := make(chan string, 2)
Cosmos Nicolaoue9c622d2015-07-10 11:09:42 -070090 v23.GetAppCycle(ctx).WaitForStop(ctx, stopChan)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -080091
92 // Blocking is used to prevent the process from exiting upon receiving a
93 // second signal or stop command while critical cleanup code is
94 // executing.
95 var blocking sync.WaitGroup
96 blockingCh := make(chan struct{})
97
98 // This is how to wait for a signal or stop command and initiate the
99 // clean shutdown steps.
100 go func() {
101 // First signal received.
102 select {
103 case sig := <-sigChan:
104 // If the developer wants to take different actions
105 // depending on the type of signal, they can do it here.
Todd Wang95873902015-05-22 14:21:30 -0700106 fmt.Fprintln(env.Stdout, "Received signal", sig)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800107 case stop := <-stopChan:
Todd Wang95873902015-05-22 14:21:30 -0700108 fmt.Fprintln(env.Stdout, "Stop", stop)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800109 }
110 // This commences the cleanup stage.
111 done.Done()
112 // Wait for a second signal or stop command, and force an exit,
113 // but only once all blocking cleanup code (if any) has
114 // completed.
115 select {
116 case <-sigChan:
117 case <-stopChan:
118 }
119 <-blockingCh
Bogdan Capritae06d7152014-11-22 09:32:05 -0800120 os.Exit(signals.DoubleStopExitCode)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800121 }()
122
123 // This communicates to the parent test driver process in our unit test
124 // that this server is ready and waiting on signals or stop commands.
125 // It's purely an artifact of our test setup.
Todd Wang95873902015-05-22 14:21:30 -0700126 fmt.Fprintln(env.Stdout, "Ready")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800127
128 // Wait for shutdown.
129 done.Wait()
130
131 // Stop the servers. In this example we stop them in goroutines to
132 // parallelize the wait, but if there was a dependency between the
133 // servers, the developer can simply stop them sequentially.
134 var waitServerStop sync.WaitGroup
135 waitServerStop.Add(2)
136 go func() {
137 server1.Stop()
138 waitServerStop.Done()
139 }()
140 go func() {
141 server2.Stop()
142 waitServerStop.Done()
143 }()
144 waitServerStop.Wait()
145
146 // This is where all cleanup code should go. By placing it at the end,
147 // we make its purpose and order of execution clear.
148
149 // This is an example of how to mix parallel and sequential cleanup
150 // steps. Most real-world servers will likely be simpler, with either
151 // just sequential or just parallel cleanup stages.
152
153 // parallelCleanup is used to wait for all goroutines executing cleanup
154 // code in parallel to finish.
155 var parallelCleanup sync.WaitGroup
156
157 // Simulate four parallel cleanup steps, two blocking and two
158 // interruptible.
159 parallelCleanup.Add(1)
160 blocking.Add(1)
161 go func() {
Todd Wang95873902015-05-22 14:21:30 -0700162 fmt.Fprintln(env.Stdout, "Parallel blocking cleanup1")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800163 blocking.Done()
164 parallelCleanup.Done()
165 }()
166
167 parallelCleanup.Add(1)
168 blocking.Add(1)
169 go func() {
Todd Wang95873902015-05-22 14:21:30 -0700170 fmt.Fprintln(env.Stdout, "Parallel blocking cleanup2")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800171 blocking.Done()
172 parallelCleanup.Done()
173 }()
174
175 parallelCleanup.Add(1)
176 go func() {
Todd Wang95873902015-05-22 14:21:30 -0700177 fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup1")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800178 parallelCleanup.Done()
179 }()
180
181 parallelCleanup.Add(1)
182 go func() {
Todd Wang95873902015-05-22 14:21:30 -0700183 fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup2")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800184 parallelCleanup.Done()
185 }()
186
187 // Simulate two sequential cleanup steps, one blocking and one
188 // interruptible.
Todd Wang95873902015-05-22 14:21:30 -0700189 fmt.Fprintln(env.Stdout, "Sequential blocking cleanup")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800190 blocking.Wait()
191 close(blockingCh)
192
Todd Wang95873902015-05-22 14:21:30 -0700193 fmt.Fprintln(env.Stdout, "Sequential interruptible cleanup")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800194
195 parallelCleanup.Wait()
196 return nil
Todd Wang95873902015-05-22 14:21:30 -0700197}, "complexServerProgram")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800198
199// simpleServerProgram demonstrates the recommended way to write a typical
200// simple server application (with one server and a clean shutdown triggered by
201// a signal or a stop command). For an example of something more involved, see
202// complexServerProgram.
Todd Wang95873902015-05-22 14:21:30 -0700203var simpleServerProgram = modules.Register(func(env *modules.Env, args ...string) error {
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800204 // Initialize the runtime. This is boilerplate.
Todd Wang60052d82015-05-22 15:00:10 -0700205 ctx, shutdown := test.V23Init()
Ankurfb9255a2015-01-21 15:29:38 -0800206 // Calling shutdown is optional, but it's a good idea to clean up, especially
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800207 // since it takes care of flushing the logs before exiting.
208 //
209 // We use defer to ensure this is the last thing in the program (to
210 // avoid shutting down the runtime while it may still be in use), and to
211 // allow it to execute even if a panic occurs down the road.
Suharsh Sivakumard5049b72015-01-21 14:11:35 -0800212 defer shutdown()
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -0800213
214 // This is part of the test setup -- we need a way to accept
215 // commands from the parent process to simulate Stop and
216 // RemoteStop commands that would normally be issued from
217 // application code.
Todd Wang95873902015-05-22 14:21:30 -0700218 defer remoteCmdLoop(ctx, env.Stdin)()
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -0800219
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800220 // Create a server, and start serving.
Matt Rosencrantz53ac5852015-09-04 15:14:54 -0700221 ctx, server, err := v23.WithNewServer(ctx, "", &dummy{}, nil)
Matt Rosencrantzbb6295d2015-06-19 15:13:58 -0700222 if err != nil {
Cosmos Nicolaoue9c622d2015-07-10 11:09:42 -0700223 ctx.Fatalf("r.NewServer error: %s", err)
Matt Rosencrantzbb6295d2015-06-19 15:13:58 -0700224 }
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800225
226 // This is how to wait for a shutdown. In this example, a shutdown
227 // comes from a signal or a stop command.
228 //
229 // Note, if the developer wants to exit immediately upon receiving a
230 // signal or stop command, they can skip this, in which case the default
231 // behavior is for the process to exit.
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -0800232 waiter := signals.ShutdownOnSignals(ctx)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800233
234 // This communicates to the parent test driver process in our unit test
235 // that this server is ready and waiting on signals or stop commands.
236 // It's purely an artifact of our test setup.
Todd Wang95873902015-05-22 14:21:30 -0700237 fmt.Fprintln(env.Stdout, "Ready")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800238
239 // Use defer for anything that should still execute even if a panic
240 // occurs.
Todd Wang95873902015-05-22 14:21:30 -0700241 defer fmt.Fprintln(env.Stdout, "Deferred cleanup")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800242
243 // Wait for shutdown.
244 sig := <-waiter
245 // The developer could take different actions depending on the type of
246 // signal.
Todd Wang95873902015-05-22 14:21:30 -0700247 fmt.Fprintln(env.Stdout, "Received signal", sig)
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800248
249 // Cleanup code starts here. Alternatively, these steps could be
250 // invoked through defer, but we list them here to make the order of
251 // operations obvious.
252
253 // Stop the server.
254 server.Stop()
255
256 // Note, this will not execute in cases of forced shutdown
257 // (e.g. SIGSTOP), when the process calls os.Exit (e.g. via log.Fatal),
258 // or when a panic occurs.
Todd Wang95873902015-05-22 14:21:30 -0700259 fmt.Fprintln(env.Stdout, "Interruptible cleanup")
Cosmos Nicolaoua429eff2014-11-19 14:02:34 -0800260
261 return nil
Todd Wang95873902015-05-22 14:21:30 -0700262}, "simpleServerProgram")