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