github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/pkg/ctl/commander.go (about)

     1  package ctl
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/Benchkram/bob/pkg/boblog"
     9  	"github.com/Benchkram/errz"
    10  )
    11  
    12  var ErrInProgress = fmt.Errorf("in progress")
    13  var ErrDone = fmt.Errorf("commander is done and can't be started")
    14  
    15  type Commander interface {
    16  	CommandTree
    17  }
    18  
    19  // commander allows to manage mutliple controls
    20  type commander struct {
    21  	// ctx to listen for execution interruption
    22  	ctx context.Context
    23  
    24  	// builder can trigger a rebuild.
    25  	builder Builder
    26  
    27  	// control is used to control the commander.
    28  	control Control
    29  
    30  	// commands are the children the commander controls.
    31  	commands []Command
    32  
    33  	// starting  blocks subssequent starting requests.
    34  	starting *Flag
    35  	// stopping blocks subssequent stopping requests.
    36  	stopping *Flag
    37  
    38  	// done indicates that the commander becomes noop.
    39  	done bool
    40  
    41  	// doneChan emits when the commander becomes noop.
    42  	doneChan chan struct{}
    43  }
    44  
    45  type Builder interface {
    46  	Build(context.Context) error
    47  }
    48  
    49  // NewCommander creates a commander object which can be started and stoped
    50  // until shutdown is called, then it becomes noop.
    51  //
    52  // The commander allows it to control multiple commands while taking
    53  // orders from a higher level instance like a TUI.
    54  //
    55  // TODO: Could be benficial for a TUI to directly control the commands.
    56  //       That needs somehow blocking of a starting/stopping of the whole commander
    57  //       while a child is doing some work. This is currently not implemented.
    58  //       It's possible to control the underlying commands directly through
    59  //       `Subcommands()` but that could probably lead to nasty start/stop loops.
    60  //  ___________             ___________             ___________
    61  // |           | Command() |           | Command() |           |
    62  // | n*command | *-------1 | commander |1--------1 |    tui    |
    63  // |           |           |           |           |           |
    64  // |___________|           |___________|           |___________|
    65  //
    66  func NewCommander(ctx context.Context, builder Builder, ctls ...Command) Commander {
    67  
    68  	c := &commander{
    69  		ctx: ctx,
    70  
    71  		builder: builder,
    72  
    73  		control:  New("commander", 0, nil, nil, nil),
    74  		commands: ctls,
    75  
    76  		starting: &Flag{},
    77  		stopping: &Flag{},
    78  
    79  		doneChan: make(chan struct{}),
    80  	}
    81  
    82  	// Listen on the control for external cmds
    83  	go func() {
    84  		restarting := Flag{}
    85  
    86  		for {
    87  			select {
    88  			case <-ctx.Done():
    89  				// wait till all cmds are done
    90  				<-c.Done()
    91  				c.control.EmitDone()
    92  				return
    93  			case s := <-c.control.Control():
    94  				switch s {
    95  				case Restart:
    96  					// Prevent a restart to happen multiple times.
    97  					// Blocks till the first restart request is finished.
    98  					done, err := restarting.InProgress()
    99  					if err != nil {
   100  						continue
   101  					}
   102  
   103  					go func() {
   104  						defer done()
   105  
   106  						err := c.Stop()
   107  						boblog.Log.Error(err, "Error on stopping comander")
   108  
   109  						// Trigger a rebuild.
   110  						err = c.builder.Build(ctx)
   111  						errz.Fatal(err)
   112  
   113  						err = c.Start()
   114  						boblog.Log.Error(err, "Error during comander run")
   115  
   116  						c.control.EmitRestarted()
   117  					}()
   118  				}
   119  			}
   120  		}
   121  	}()
   122  
   123  	// Shutdown each control
   124  	// on a canceled context
   125  	go func() {
   126  		<-ctx.Done()
   127  		c.shutdown()
   128  	}()
   129  
   130  	return c
   131  }
   132  
   133  // Subcommands allows direct access to the underlying commands.
   134  // !!!Should used with care!!!
   135  // See the comment from `NewCommander()`
   136  func (c *commander) Subcommands() []Command {
   137  	return c.commands
   138  }
   139  
   140  // Start cmds in inverse order.
   141  // Blocks subsquent calls until the first one is completed.
   142  func (c *commander) Start() (err error) {
   143  	return c.start()
   144  }
   145  
   146  func (c *commander) start() (err error) {
   147  	if c.done {
   148  		return ErrDone
   149  	}
   150  
   151  	done, err := c.starting.InProgress()
   152  	if err != nil {
   153  		return err
   154  	}
   155  	defer done()
   156  
   157  	for i := len(c.commands) - 1; i >= 0; i-- {
   158  		ctl := c.commands[i]
   159  		err = ctl.Start()
   160  		if err != nil {
   161  			return err
   162  		}
   163  	}
   164  
   165  	return err
   166  }
   167  
   168  // Stop children from top to bottom.
   169  // Blocks subsquent calls until the first one is completed.
   170  func (c *commander) Stop() (err error) {
   171  	return c.stop()
   172  }
   173  
   174  // stop children, starting from top.
   175  func (c *commander) stop() (err error) {
   176  
   177  	done, err := c.stopping.InProgress()
   178  	if err != nil {
   179  		return err
   180  	}
   181  	defer done()
   182  
   183  	for _, v := range c.commands {
   184  		if e := v.Stop(); err != nil {
   185  			err = stackErrors(err, e)
   186  		}
   187  	}
   188  
   189  	return err
   190  }
   191  
   192  func (c *commander) Done() <-chan struct{} {
   193  	return c.doneChan
   194  }
   195  
   196  // shutdown forwards the signal to the children.
   197  func (c *commander) shutdown() {
   198  	for _, v := range c.commands {
   199  		_ = v.Shutdown()
   200  	}
   201  	c.done = true
   202  	close(c.doneChan)
   203  }
   204  
   205  func (c *commander) Name() string {
   206  	return c.control.Name()
   207  }
   208  func (c *commander) Restart() error {
   209  	return c.control.Restart()
   210  }
   211  func (c *commander) Running() bool {
   212  	return c.control.Running()
   213  }
   214  func (c *commander) Shutdown() error {
   215  	return c.control.Shutdown()
   216  }
   217  func (c *commander) Stdout() io.Reader {
   218  	return c.control.Stdout()
   219  }
   220  func (c *commander) Stderr() io.Reader {
   221  	return c.control.Stderr()
   222  }
   223  func (c *commander) Stdin() io.Writer {
   224  	return c.control.Stdin()
   225  }