github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/test/pseudo/cmd/svc/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "flag" 7 "fmt" 8 "io" 9 "os" 10 "os/signal" 11 "path" 12 "syscall" 13 "time" 14 15 "github.com/ActiveState/cli/internal/ipc" 16 "github.com/ActiveState/cli/internal/svcctl" 17 "github.com/ActiveState/cli/test/pseudo/cmd/internal/serve" 18 intsvcctl "github.com/ActiveState/cli/test/pseudo/cmd/internal/svcctl" 19 ) 20 21 type namedClose struct { 22 name string 23 io.Closer 24 } 25 26 type gracefulShutdowner interface { 27 Shutdown() 28 Wait() error 29 } 30 31 type gracefulShutdownerWrap struct { 32 gracefulShutdowner 33 } 34 35 func (s gracefulShutdownerWrap) Close() error { 36 s.gracefulShutdowner.Shutdown() 37 38 return s.gracefulShutdowner.Wait() 39 } 40 41 func main() { 42 if exitCode, err := run(); err != nil { 43 cmd := path.Base(os.Args[0]) 44 fmt.Fprintf(os.Stderr, "%s: %s\n", cmd, err) 45 46 if errors.Is(err, ipc.ErrInUse) { 47 exitCode = 7 48 } 49 os.Exit(exitCode) 50 } 51 } 52 53 func run() (int, error) { 54 var ( 55 channel = "default" 56 svcName = "svc" 57 ) 58 59 defer fmt.Printf("%s: goodbye\n", svcName) 60 61 flag.StringVar(&channel, "c", channel, "channel name") 62 flag.Parse() 63 64 ctx, cancel := context.WithCancel(context.Background()) 65 defer cancel() 66 67 httpSrv := serve.New() 68 addr, err := httpSrv.Run() 69 if err != nil { 70 return 1, err 71 } 72 73 spath := intsvcctl.NewIPCSockPath() 74 spath.AppChannel = channel 75 reqHandlers := []ipc.RequestHandler{ 76 svcctl.HTTPAddrHandler(addr), 77 } 78 ipcSrv := ipc.NewServer(ctx, spath, reqHandlers...) 79 ipcClient := ipc.NewClient(spath) 80 if err := ipcSrv.Start(); err != nil { 81 return 2, err 82 } 83 84 errs := make(chan error) 85 86 callOnSysSigs(ctx, svcName, cancel) 87 callWhenNotVerified(ctx, errs, svcName, addr, ipcClient, cancel) 88 89 return closeOnCancel( 90 ctx, 91 svcName, 92 namedClose{"http", httpSrv}, 93 namedClose{"ipc", gracefulShutdownerWrap{ipcSrv}}, 94 ) 95 } 96 97 func closeOnCancel(ctx context.Context, svcName string, ncs ...namedClose) (int, error) { 98 <-ctx.Done() 99 100 var exitCode int 101 var retErr error 102 103 for _, nc := range ncs { 104 fmt.Printf("%s: closing %s\n", svcName, nc.name) 105 if err := nc.Close(); err != nil { 106 fmt.Fprintf(os.Stderr, "%s: %s\n", svcName, err) 107 if retErr != nil { 108 exitCode = 3 109 retErr = err 110 } 111 } 112 } 113 114 return exitCode, retErr 115 } 116 117 func callOnSysSigs(ctx context.Context, svcName string, fn func()) { 118 sigs := make(chan os.Signal, 1) 119 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 120 121 go func() { 122 defer close(sigs) 123 124 select { 125 case <-ctx.Done(): 126 return 127 case sig, ok := <-sigs: 128 if !ok { 129 return 130 } 131 132 fmt.Printf("%s: handling signal: %s\n", svcName, sig) 133 fn() 134 } 135 }() 136 } 137 138 func callWhenNotVerified(ctx context.Context, errs chan error, svcName, addr string, ipComm svcctl.IPCommunicator, fn func()) { 139 go func() { 140 select { 141 case <-ctx.Done(): 142 return 143 case <-time.After(time.Second * 3): 144 checkedAddr, err := svcctl.LocateHTTP(ipComm) 145 if err == nil && checkedAddr != addr { 146 err = fmt.Errorf("checked addr %q does not match current %q", checkedAddr, addr) 147 } 148 if err != nil { 149 errs <- err 150 fn() 151 } 152 } 153 }() 154 }