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  }