github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/utils/svcs/controller.go (about) 1 // Copyright 2023 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package svcs 16 17 import ( 18 "context" 19 "errors" 20 "sync" 21 "sync/atomic" 22 ) 23 24 // A Service is a runnable unit of functionality that a Controller can 25 // take responsibility for. It has an |Init| function, which can error, and 26 // which should do all of the initialization and validation work necessary to 27 // bring the service up. It has a |Run| function, which will be called in a 28 // separate go-routine and should run and provide the functionality associated 29 // with the service until the |Stop| function is called. 30 type Service interface { 31 Init(context.Context) error 32 Run(context.Context) 33 Stop() error 34 } 35 36 // AnonService is a simple struct for building Service instances with lambdas 37 // or funcs, instead of creating an interface implementation. 38 type AnonService struct { 39 InitF func(context.Context) error 40 RunF func(context.Context) 41 StopF func() error 42 } 43 44 func (a AnonService) Init(ctx context.Context) error { 45 if a.InitF == nil { 46 return nil 47 } 48 return a.InitF(ctx) 49 } 50 51 func (a AnonService) Run(ctx context.Context) { 52 if a.RunF == nil { 53 return 54 } 55 a.RunF(ctx) 56 } 57 58 func (a AnonService) Stop() error { 59 if a.StopF == nil { 60 return nil 61 } 62 return a.StopF() 63 } 64 65 // ServiceState is a small abstraction so that a service implementation can 66 // easily track what state it is in and can make decisions about what to do 67 // based on what state it is coming from. In particular, it's not rare for a 68 // |Close| implementation to need to do something different based on whether 69 // the service is only init'd or if it is running. It's also not rare for a 70 // service to decide it needs to do nothing in Init, in which case it can leave 71 // the service in Off, and the Run and Close methods can check that to ensure 72 // they do not do anything either. 73 type ServiceState uint32 74 75 const ( 76 ServiceState_Off ServiceState = iota 77 ServiceState_Init 78 ServiceState_Run 79 ServiceState_Stopped 80 ) 81 82 func (ss *ServiceState) Swap(new ServiceState) (old ServiceState) { 83 return ServiceState(atomic.SwapUint32((*uint32)(ss), uint32(new))) 84 } 85 86 func (ss *ServiceState) CompareAndSwap(old, new ServiceState) (swapped bool) { 87 return atomic.CompareAndSwapUint32((*uint32)(ss), uint32(old), uint32(new)) 88 } 89 90 // A Controller is responsible for initializing a number of registered 91 // services, running them all, and stopping them all when requested. Services 92 // are registered with |Register(Service)|. When |Start| is called, the 93 // services are all initialized, in the order of their registration, and if 94 // every service initializes successfully, they are |Run| concurrently. When 95 // |Stop| is called, services are stopped in reverse-registration order. |Stop| 96 // returns once the corresponding |Stop| method on all successfully |Init|ed 97 // services has returned. |Stop| does not explicitly block for the goroutines 98 // where the |Run| methods are called to complete. Typically a Service's 99 // |Stop| function should ensure that the |Run| method has completed before 100 // returning. 101 // 102 // Any attempt to register a service after |Start| or |Stop| has been called 103 // will return an error. 104 // 105 // If an error occurs when initializing the services of a Controller, the 106 // Stop functions of any already initialized Services are called in 107 // reverse-order. The error which caused the initialization error is returned. 108 // 109 // In the case that all Services Init successfully, the error returned from 110 // |Start| is the first non-nil error which is returned from the |Stop| 111 // functions, in the order they are called. 112 // 113 // If |Stop| is called before |Start|, |Start| will return an error. |Register| 114 // will also begin returning an error after |Stop| is called, if it is called 115 // before |Start|. 116 // 117 // |WaitForStart| can be called at any time on a Controller. It will block 118 // until |Start| is called. After |Start| is called, if all the services 119 // succesfully initialize, it will return |nil|. Otherwise it will return the 120 // same error |Start| returned. 121 // 122 // |WaitForStop| can be called at any time on a Controller. It will block until 123 // |Start| is called and initialization fails, or until |Stop| is called. It 124 // will return the same error which |Start| returned. 125 type Controller struct { 126 mu sync.Mutex 127 services []Service 128 initErr error 129 stopErr error 130 startCh chan struct{} 131 stopCh chan struct{} 132 stoppedCh chan struct{} 133 state controllerState 134 } 135 136 type controllerState int 137 138 const ( 139 controllerState_created controllerState = iota 140 controllerState_starting controllerState = iota 141 controllerState_running controllerState = iota 142 controllerState_stopping controllerState = iota 143 controllerState_stopped controllerState = iota 144 ) 145 146 func NewController() *Controller { 147 return &Controller{ 148 startCh: make(chan struct{}), 149 stopCh: make(chan struct{}), 150 stoppedCh: make(chan struct{}), 151 } 152 } 153 154 func (c *Controller) WaitForStart() error { 155 <-c.startCh 156 c.mu.Lock() 157 err := c.initErr 158 c.mu.Unlock() 159 return err 160 } 161 162 func (c *Controller) WaitForStop() error { 163 <-c.stoppedCh 164 c.mu.Lock() 165 var err error 166 if c.initErr != nil { 167 err = c.initErr 168 } else if c.stopErr != nil { 169 err = c.stopErr 170 } 171 c.mu.Unlock() 172 return err 173 } 174 175 func (c *Controller) Register(svc Service) error { 176 c.mu.Lock() 177 if c.state != controllerState_created { 178 c.mu.Unlock() 179 return errors.New("Controller: cannot Register a service on a controller which was already started or stopped") 180 } 181 c.services = append(c.services, svc) 182 c.mu.Unlock() 183 return nil 184 } 185 186 func (c *Controller) Stop() { 187 c.mu.Lock() 188 if c.state == controllerState_created { 189 // Nothing ever ran, we can transition directly to stopped. 190 // TODO: Is a more correct contract to put an error into initErr here? The services never started successfully... 191 c.state = controllerState_stopped 192 close(c.startCh) 193 close(c.stoppedCh) 194 c.mu.Unlock() 195 return 196 } else if c.state == controllerState_stopped { 197 // We already stopped. 198 c.mu.Unlock() 199 return 200 } else if c.state != controllerState_stopping { 201 // We should only do this transition once. We signal to |Start| 202 // by closing the |stopCh|. 203 close(c.stopCh) 204 c.state = controllerState_stopping 205 c.mu.Unlock() 206 } 207 <-c.stoppedCh 208 } 209 210 func (c *Controller) Start(ctx context.Context) error { 211 c.mu.Lock() 212 if c.state != controllerState_created { 213 c.mu.Unlock() 214 return errors.New("Controller: cannot start service controller after is has been started or stopped") 215 } 216 c.state = controllerState_starting 217 svcs := make([]Service, len(c.services)) 218 copy(svcs, c.services) 219 c.mu.Unlock() 220 for i, s := range svcs { 221 err := s.Init(ctx) 222 if err != nil { 223 for j := i - 1; j >= 0; j-- { 224 svcs[j].Stop() 225 } 226 c.mu.Lock() 227 c.state = controllerState_stopped 228 c.initErr = err 229 close(c.startCh) 230 close(c.stoppedCh) 231 c.mu.Unlock() 232 return err 233 } 234 } 235 close(c.startCh) 236 c.mu.Lock() 237 if c.state == controllerState_starting { 238 c.state = controllerState_running 239 c.mu.Unlock() 240 for _, s := range svcs { 241 go s.Run(ctx) 242 } 243 <-c.stopCh 244 } else { 245 // We were stopped while initializing. Start shutting things down. 246 c.mu.Unlock() 247 } 248 var stopErr error 249 for i := len(svcs) - 1; i >= 0; i-- { 250 err := svcs[i].Stop() 251 if err != nil && stopErr == nil { 252 stopErr = err 253 } 254 } 255 c.mu.Lock() 256 if stopErr != nil { 257 c.stopErr = stopErr 258 } 259 c.state = controllerState_stopped 260 close(c.stoppedCh) 261 c.mu.Unlock() 262 return stopErr 263 }