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 }