blob: 66a01222cf64a28d6e483bc284ef52ef2db1b649 [file] [log] [blame]
package runtime
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"veyron2/rt"
)
// complexServerProgram demonstrates the recommended way to write a more complex
// server application (with several servers, a mix of interruptible and blocking
// cleanup, and parallel and sequential cleanup execution). For a more typical
// server, see simpleServerProgram.
func complexServerProgram() {
// Initialize the runtime. This is boilerplate.
r := rt.Init()
// r.Cleanup is optional, but it's a good idea to clean up, especially
// since it takes care of flushing the logs before exiting.
defer r.Cleanup()
// Create a couple servers, and start serving.
server1 := makeServer()
server2 := makeServer()
// This is how to wait for a shutdown. In this example, a shutdown
// comes from a signal or a stop command.
var done sync.WaitGroup
done.Add(1)
// This is how to configure signal handling to allow clean shutdown.
sigChan := make(chan os.Signal, 2)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
// This is how to configure handling of stop commands to allow clean
// shutdown.
stopChan := make(chan string, 2)
r.WaitForStop(stopChan)
// Blocking is used to prevent the process from exiting upon receiving a
// second signal or stop command while critical cleanup code is
// executing.
var blocking sync.WaitGroup
blockingCh := make(chan struct{})
// This is how to wait for a signal or stop command and initiate the
// clean shutdown steps.
go func() {
// First signal received.
select {
case sig := <-sigChan:
// If the developer wants to take different actions
// depending on the type of signal, they can do it here.
fmt.Println("Received signal", sig)
case stop := <-stopChan:
fmt.Println("Stop", stop)
}
// This commences the cleanup stage.
done.Done()
// Wait for a second signal or stop command, and force an exit,
// but only once all blocking cleanup code (if any) has
// completed.
select {
case <-sigChan:
case <-stopChan:
}
<-blockingCh
os.Exit(1)
}()
// This communicates to the parent test driver process in our unit test
// that this server is ready and waiting on signals or stop commands.
// It's purely an artifact of our test setup.
fmt.Println("Ready")
// Wait for shutdown.
done.Wait()
// Stop the servers. In this example we stop them in goroutines to
// parallelize the wait, but if there was a dependency between the
// servers, the developer can simply stop them sequentially.
var waitServerStop sync.WaitGroup
waitServerStop.Add(2)
go func() {
server1.Stop()
waitServerStop.Done()
}()
go func() {
server2.Stop()
waitServerStop.Done()
}()
waitServerStop.Wait()
// This is where all cleanup code should go. By placing it at the end,
// we make its purpose and order of execution clear.
// This is an example of how to mix parallel and sequential cleanup
// steps. Most real-world servers will likely be simpler, with either
// just sequential or just parallel cleanup stages.
// parallelCleanup is used to wait for all goroutines executing cleanup
// code in parallel to finish.
var parallelCleanup sync.WaitGroup
// Simulate four parallel cleanup steps, two blocking and two
// interruptible.
parallelCleanup.Add(1)
blocking.Add(1)
go func() {
fmt.Println("Parallel blocking cleanup1")
blocking.Done()
parallelCleanup.Done()
}()
parallelCleanup.Add(1)
blocking.Add(1)
go func() {
fmt.Println("Parallel blocking cleanup2")
blocking.Done()
parallelCleanup.Done()
}()
parallelCleanup.Add(1)
go func() {
fmt.Println("Parallel interruptible cleanup1")
parallelCleanup.Done()
}()
parallelCleanup.Add(1)
go func() {
fmt.Println("Parallel interruptible cleanup2")
parallelCleanup.Done()
}()
// Simulate two sequential cleanup steps, one blocking and one
// interruptible.
fmt.Println("Sequential blocking cleanup")
blocking.Wait()
close(blockingCh)
fmt.Println("Sequential interruptible cleanup")
parallelCleanup.Wait()
}