// Package daemon implements the isle daemon, which is a long-running service // managing all isle background tasks and sub-processes for a single cluster. package daemon import ( "context" "fmt" "io" "isle/bootstrap" "os" "code.betamike.com/micropelago/pmux/pmuxlib" "dev.mediocregopher.com/mediocre-go-lib.git/mlog" ) // Daemon presents all functionality required for client frontends to interact // with isle, typically via the unix socket. type Daemon interface { // GetGarageBootstrapHosts loads (and verifies) the .json.signed // file for all hosts stored in garage. GetGarageBootstrapHosts( ctx context.Context, ) ( map[string]bootstrap.Host, error, ) // Shutdown blocks until all resources held or created by the daemon, // including child processes it has started, have been cleaned up. // // If this returns an error then it's possible that child processes are // still running and are no longer managed. Shutdown() error } // Opts are optional parameters which can be passed in when initializing a new // Daemon instance. A nil Opts is equivalent to a zero value. type Opts struct { // SkipHostBootstrapPush, if set, will cause the Daemon to not push the // bootstrap to garage upon a successful initialization. SkipHostBootstrapPush bool // Stdout and Stderr are what the associated outputs from child processes // will be directed to. Stdout, Stderr io.Writer EnvVars EnvVars // Defaults to that returned by GetEnvVars. } func (o *Opts) withDefaults() *Opts { if o == nil { o = new(Opts) } if o.Stdout == nil { o.Stdout = os.Stdout } if o.Stderr == nil { o.Stderr = os.Stderr } if o.EnvVars == (EnvVars{}) { o.EnvVars = GetEnvVars() } return o } type daemon struct { logger *mlog.Logger config Config hostBootstrap bootstrap.Bootstrap binDirPath string opts Opts pmuxCancelFn context.CancelFunc pmuxStoppedCh chan struct{} } // New initialized and returns a Daemon. If initialization fails an error is // returned. func New( ctx context.Context, logger *mlog.Logger, config Config, hostBootstrap bootstrap.Bootstrap, binDirPath string, opts *Opts, ) ( Daemon, error, ) { opts = opts.withDefaults() pmuxCtx, pmuxCancelFn := context.WithCancel(context.Background()) d := &daemon{ logger: logger, config: config, hostBootstrap: hostBootstrap, binDirPath: binDirPath, opts: *opts, pmuxCancelFn: pmuxCancelFn, pmuxStoppedCh: make(chan struct{}), } pmuxConfig, err := d.newPmuxConfig() if err != nil { return nil, fmt.Errorf("generating pmux config: %w", err) } go func() { defer close(d.pmuxStoppedCh) pmuxlib.Run(pmuxCtx, d.opts.Stdout, d.opts.Stderr, pmuxConfig) d.logger.Debug(pmuxCtx, "pmux stopped") }() if initErr := d.postPmuxInit(ctx); initErr != nil { logger.Warn(ctx, "failed to initialize daemon, shutting down child processes", err) if err := d.Shutdown(); err != nil { panic(fmt.Sprintf( "failed to shut down child processes after initialization"+ " error, there may be zombie children leftover."+ " Original error: %v", initErr, )) } return nil, initErr } return d, nil } func (d *daemon) Shutdown() error { d.pmuxCancelFn() <-d.pmuxStoppedCh return nil }