Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // 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 Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 5 | package rt_test |
| 6 | |
| 7 | import ( |
| 8 | "bufio" |
| 9 | "fmt" |
| 10 | "io" |
| 11 | "os" |
| 12 | "os/signal" |
| 13 | "sync" |
| 14 | "syscall" |
| 15 | |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 16 | "v.io/v23" |
| 17 | "v.io/v23/context" |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 18 | "v.io/v23/rpc" |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 19 | "v.io/x/ref/lib/signals" |
Suharsh Sivakumar | dcc11d7 | 2015-05-11 12:19:20 -0700 | [diff] [blame] | 20 | _ "v.io/x/ref/runtime/factories/generic" |
Cosmos Nicolaou | 1381f8a | 2015-03-13 09:40:34 -0700 | [diff] [blame] | 21 | "v.io/x/ref/test" |
| 22 | "v.io/x/ref/test/modules" |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 23 | ) |
| 24 | |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 25 | type dummy struct{} |
| 26 | |
Todd Wang | 54feabe | 2015-04-15 23:38:26 -0700 | [diff] [blame] | 27 | func (*dummy) Echo(*context.T, rpc.ServerCall) error { return nil } |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 28 | |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 29 | // remoteCmdLoop listens on stdin and interprets commands sent over stdin (from |
| 30 | // the parent process). |
Suharsh Sivakumar | 946f64d | 2015-01-08 10:48:13 -0800 | [diff] [blame] | 31 | func remoteCmdLoop(ctx *context.T, stdin io.Reader) func() { |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 32 | done := make(chan struct{}) |
| 33 | go func() { |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 34 | scanner := bufio.NewScanner(stdin) |
| 35 | for scanner.Scan() { |
| 36 | switch scanner.Text() { |
| 37 | case "stop": |
Cosmos Nicolaou | e9c622d | 2015-07-10 11:09:42 -0700 | [diff] [blame] | 38 | v23.GetAppCycle(ctx).Stop(ctx) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 39 | case "forcestop": |
| 40 | fmt.Println("straight exit") |
Cosmos Nicolaou | e9c622d | 2015-07-10 11:09:42 -0700 | [diff] [blame] | 41 | v23.GetAppCycle(ctx).ForceStop(ctx) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 42 | case "close": |
Bogdan Caprita | e06d715 | 2014-11-22 09:32:05 -0800 | [diff] [blame] | 43 | close(done) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 44 | 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 55 | var complexServerProgram = modules.Register(func(env *modules.Env, args ...string) error { |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 56 | // Initialize the runtime. This is boilerplate. |
Todd Wang | 60052d8 | 2015-05-22 15:00:10 -0700 | [diff] [blame] | 57 | ctx, shutdown := test.V23Init() |
Suharsh Sivakumar | d5049b7 | 2015-01-21 14:11:35 -0800 | [diff] [blame] | 58 | // 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 Sivakumar | 946f64d | 2015-01-08 10:48:13 -0800 | [diff] [blame] | 61 | |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 62 | // 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 66 | defer remoteCmdLoop(ctx, env.Stdin)() |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 67 | |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 68 | // Create a couple servers, and start serving. |
Matt Rosencrantz | 53ac585 | 2015-09-04 15:14:54 -0700 | [diff] [blame] | 69 | ctx, server1, err := v23.WithNewServer(ctx, "", &dummy{}, nil) |
Matt Rosencrantz | bb6295d | 2015-06-19 15:13:58 -0700 | [diff] [blame] | 70 | if err != nil { |
Cosmos Nicolaou | e9c622d | 2015-07-10 11:09:42 -0700 | [diff] [blame] | 71 | ctx.Fatalf("r.NewServer error: %s", err) |
Matt Rosencrantz | bb6295d | 2015-06-19 15:13:58 -0700 | [diff] [blame] | 72 | } |
Matt Rosencrantz | 53ac585 | 2015-09-04 15:14:54 -0700 | [diff] [blame] | 73 | ctx, server2, err := v23.WithNewServer(ctx, "", &dummy{}, nil) |
Matt Rosencrantz | bb6295d | 2015-06-19 15:13:58 -0700 | [diff] [blame] | 74 | if err != nil { |
Cosmos Nicolaou | e9c622d | 2015-07-10 11:09:42 -0700 | [diff] [blame] | 75 | ctx.Fatalf("r.NewServer error: %s", err) |
Matt Rosencrantz | bb6295d | 2015-06-19 15:13:58 -0700 | [diff] [blame] | 76 | } |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 77 | |
| 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 Nicolaou | e9c622d | 2015-07-10 11:09:42 -0700 | [diff] [blame] | 90 | v23.GetAppCycle(ctx).WaitForStop(ctx, stopChan) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 91 | |
| 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 106 | fmt.Fprintln(env.Stdout, "Received signal", sig) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 107 | case stop := <-stopChan: |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 108 | fmt.Fprintln(env.Stdout, "Stop", stop) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 109 | } |
| 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 Caprita | e06d715 | 2014-11-22 09:32:05 -0800 | [diff] [blame] | 120 | os.Exit(signals.DoubleStopExitCode) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 121 | }() |
| 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 126 | fmt.Fprintln(env.Stdout, "Ready") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 127 | |
| 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 162 | fmt.Fprintln(env.Stdout, "Parallel blocking cleanup1") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 163 | blocking.Done() |
| 164 | parallelCleanup.Done() |
| 165 | }() |
| 166 | |
| 167 | parallelCleanup.Add(1) |
| 168 | blocking.Add(1) |
| 169 | go func() { |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 170 | fmt.Fprintln(env.Stdout, "Parallel blocking cleanup2") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 171 | blocking.Done() |
| 172 | parallelCleanup.Done() |
| 173 | }() |
| 174 | |
| 175 | parallelCleanup.Add(1) |
| 176 | go func() { |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 177 | fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup1") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 178 | parallelCleanup.Done() |
| 179 | }() |
| 180 | |
| 181 | parallelCleanup.Add(1) |
| 182 | go func() { |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 183 | fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup2") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 184 | parallelCleanup.Done() |
| 185 | }() |
| 186 | |
| 187 | // Simulate two sequential cleanup steps, one blocking and one |
| 188 | // interruptible. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 189 | fmt.Fprintln(env.Stdout, "Sequential blocking cleanup") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 190 | blocking.Wait() |
| 191 | close(blockingCh) |
| 192 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 193 | fmt.Fprintln(env.Stdout, "Sequential interruptible cleanup") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 194 | |
| 195 | parallelCleanup.Wait() |
| 196 | return nil |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 197 | }, "complexServerProgram") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 198 | |
| 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 203 | var simpleServerProgram = modules.Register(func(env *modules.Env, args ...string) error { |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 204 | // Initialize the runtime. This is boilerplate. |
Todd Wang | 60052d8 | 2015-05-22 15:00:10 -0700 | [diff] [blame] | 205 | ctx, shutdown := test.V23Init() |
Ankur | fb9255a | 2015-01-21 15:29:38 -0800 | [diff] [blame] | 206 | // Calling shutdown is optional, but it's a good idea to clean up, especially |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 207 | // 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 Sivakumar | d5049b7 | 2015-01-21 14:11:35 -0800 | [diff] [blame] | 212 | defer shutdown() |
Suharsh Sivakumar | 946f64d | 2015-01-08 10:48:13 -0800 | [diff] [blame] | 213 | |
| 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 218 | defer remoteCmdLoop(ctx, env.Stdin)() |
Suharsh Sivakumar | 946f64d | 2015-01-08 10:48:13 -0800 | [diff] [blame] | 219 | |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 220 | // Create a server, and start serving. |
Matt Rosencrantz | 53ac585 | 2015-09-04 15:14:54 -0700 | [diff] [blame] | 221 | ctx, server, err := v23.WithNewServer(ctx, "", &dummy{}, nil) |
Matt Rosencrantz | bb6295d | 2015-06-19 15:13:58 -0700 | [diff] [blame] | 222 | if err != nil { |
Cosmos Nicolaou | e9c622d | 2015-07-10 11:09:42 -0700 | [diff] [blame] | 223 | ctx.Fatalf("r.NewServer error: %s", err) |
Matt Rosencrantz | bb6295d | 2015-06-19 15:13:58 -0700 | [diff] [blame] | 224 | } |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 225 | |
| 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 Sivakumar | 946f64d | 2015-01-08 10:48:13 -0800 | [diff] [blame] | 232 | waiter := signals.ShutdownOnSignals(ctx) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 233 | |
| 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 237 | fmt.Fprintln(env.Stdout, "Ready") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 238 | |
| 239 | // Use defer for anything that should still execute even if a panic |
| 240 | // occurs. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 241 | defer fmt.Fprintln(env.Stdout, "Deferred cleanup") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 242 | |
| 243 | // Wait for shutdown. |
| 244 | sig := <-waiter |
| 245 | // The developer could take different actions depending on the type of |
| 246 | // signal. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 247 | fmt.Fprintln(env.Stdout, "Received signal", sig) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 248 | |
| 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 Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 259 | fmt.Fprintln(env.Stdout, "Interruptible cleanup") |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 260 | |
| 261 | return nil |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 262 | }, "simpleServerProgram") |