Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 1 | package rt_test |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "fmt" |
| 6 | "io" |
| 7 | "os" |
| 8 | "os/signal" |
| 9 | "sync" |
| 10 | "syscall" |
| 11 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 12 | "v.io/core/veyron2" |
| 13 | "v.io/core/veyron2/ipc" |
| 14 | "v.io/core/veyron2/rt" |
| 15 | "v.io/core/veyron2/vlog" |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 16 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 17 | "v.io/core/veyron/lib/modules" |
| 18 | "v.io/core/veyron/lib/signals" |
| 19 | "v.io/core/veyron/profiles" |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 20 | ) |
| 21 | |
| 22 | func init() { |
| 23 | modules.RegisterChild("simpleServerProgram", "", simpleServerProgram) |
| 24 | modules.RegisterChild("complexServerProgram", "", complexServerProgram) |
| 25 | } |
| 26 | |
| 27 | type dummy struct{} |
| 28 | |
| 29 | func (*dummy) Echo(ipc.ServerContext) error { return nil } |
| 30 | |
| 31 | // makeServer sets up a simple dummy server. |
Matt Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 32 | func makeServer(r veyron2.Runtime) ipc.Server { |
| 33 | server, err := r.NewServer() |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 34 | 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 Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 48 | func remoteCmdLoop(r veyron2.Runtime, stdin io.Reader) func() { |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 49 | done := make(chan struct{}) |
| 50 | go func() { |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 51 | scanner := bufio.NewScanner(stdin) |
| 52 | for scanner.Scan() { |
| 53 | switch scanner.Text() { |
| 54 | case "stop": |
Matt Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 55 | r.AppCycle().Stop() |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 56 | case "forcestop": |
| 57 | fmt.Println("straight exit") |
Matt Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 58 | r.AppCycle().ForceStop() |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 59 | case "close": |
Bogdan Caprita | e06d715 | 2014-11-22 09:32:05 -0800 | [diff] [blame] | 60 | close(done) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 61 | 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. |
| 72 | func complexServerProgram(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error { |
| 73 | // Initialize the runtime. This is boilerplate. |
Matt Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 74 | r, err := rt.New() |
| 75 | if err != nil { |
| 76 | vlog.Fatalf("Could not initialize runtime: %s", err) |
| 77 | } |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 78 | |
| 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 Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 83 | defer remoteCmdLoop(r, stdin)() |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 84 | |
| 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 Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 90 | server1 := makeServer(r) |
| 91 | server2 := makeServer(r) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 92 | |
| 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 Caprita | e06d715 | 2014-11-22 09:32:05 -0800 | [diff] [blame] | 135 | os.Exit(signals.DoubleStopExitCode) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 136 | }() |
| 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. |
| 218 | func simpleServerProgram(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error { |
| 219 | // Initialize the runtime. This is boilerplate. |
Matt Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 220 | r, err := rt.New() |
| 221 | if err != nil { |
| 222 | vlog.Fatalf("Could not initialize runtime: %s", err) |
| 223 | } |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 224 | |
| 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 Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 229 | defer remoteCmdLoop(r, stdin)() |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 230 | |
| 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 Rosencrantz | 0610a23 | 2014-12-04 10:26:39 -0800 | [diff] [blame] | 240 | server := makeServer(r) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 241 | |
| 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 Rosencrantz | c7fecf1 | 2014-11-27 19:58:43 -0800 | [diff] [blame] | 248 | waiter := signals.ShutdownOnSignals(r) |
Cosmos Nicolaou | a429eff | 2014-11-19 14:02:34 -0800 | [diff] [blame] | 249 | |
| 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 | } |