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  }