github.com/weaviate/weaviate@v1.24.6/entities/cyclemanager/cyclemanager.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package cyclemanager
    13  
    14  import (
    15  	"context"
    16  	"fmt"
    17  	"runtime"
    18  	"sync"
    19  
    20  	"github.com/sirupsen/logrus"
    21  	enterrors "github.com/weaviate/weaviate/entities/errors"
    22  )
    23  
    24  var _NUMCPU = runtime.NumCPU()
    25  
    26  type (
    27  	// indicates whether cyclemanager's stop was requested to allow safely
    28  	// abort execution of CycleCallback and stop cyclemanager earlier
    29  	ShouldAbortCallback func() bool
    30  	// return value indicates whether actual work was done in the cycle
    31  	CycleCallback func(shouldAbort ShouldAbortCallback) bool
    32  )
    33  
    34  type CycleManager interface {
    35  	Start()
    36  	Stop(ctx context.Context) chan bool
    37  	StopAndWait(ctx context.Context) error
    38  	Running() bool
    39  }
    40  
    41  type cycleManager struct {
    42  	sync.RWMutex
    43  
    44  	cycleCallback CycleCallback
    45  	cycleTicker   CycleTicker
    46  	running       bool
    47  	stopSignal    chan struct{}
    48  
    49  	stopContexts []context.Context
    50  	stopResults  []chan bool
    51  
    52  	logger logrus.FieldLogger
    53  }
    54  
    55  func NewManager(cycleTicker CycleTicker, cycleCallback CycleCallback, logger logrus.FieldLogger) CycleManager {
    56  	return &cycleManager{
    57  		cycleCallback: cycleCallback,
    58  		cycleTicker:   cycleTicker,
    59  		running:       false,
    60  		stopSignal:    make(chan struct{}, 1),
    61  		logger:        logger,
    62  	}
    63  }
    64  
    65  // Starts instance, does not block
    66  // Does nothing if instance is already started
    67  func (c *cycleManager) Start() {
    68  	c.Lock()
    69  	defer c.Unlock()
    70  
    71  	if c.running {
    72  		return
    73  	}
    74  
    75  	enterrors.GoWrapper(func() {
    76  		c.cycleTicker.Start()
    77  		defer c.cycleTicker.Stop()
    78  
    79  		for {
    80  			if c.isStopRequested() {
    81  				c.Lock()
    82  				if c.shouldStop() {
    83  					c.handleStopRequest(true)
    84  					c.Unlock()
    85  					break
    86  				}
    87  				c.handleStopRequest(false)
    88  				c.Unlock()
    89  				continue
    90  			}
    91  			c.cycleTicker.CycleExecuted(c.cycleCallback(c.shouldAbortCycleCallback))
    92  		}
    93  	}, c.logger)
    94  
    95  	c.running = true
    96  }
    97  
    98  // Stops running instance, does not block
    99  // Returns channel with final stop result - true / false
   100  //
   101  // If given context is cancelled before it is handled by stop logic, instance is not stopped
   102  // If called multiple times, all contexts have to be cancelled to cancel stop
   103  // (any valid will result in stopping instance)
   104  // stopResult is the same (consistent) for multiple calls
   105  func (c *cycleManager) Stop(ctx context.Context) (stopResult chan bool) {
   106  	c.Lock()
   107  	defer c.Unlock()
   108  
   109  	stopResult = make(chan bool, 1)
   110  	if !c.running {
   111  		stopResult <- true
   112  		close(stopResult)
   113  		return stopResult
   114  	}
   115  
   116  	if len(c.stopContexts) == 0 {
   117  		defer func() {
   118  			c.stopSignal <- struct{}{}
   119  		}()
   120  	}
   121  	c.stopContexts = append(c.stopContexts, ctx)
   122  	c.stopResults = append(c.stopResults, stopResult)
   123  
   124  	return stopResult
   125  }
   126  
   127  // Stops running instance, waits for stop to occur or context to expire (which comes first)
   128  // Returns error if instance was not stopped
   129  func (c *cycleManager) StopAndWait(ctx context.Context) error {
   130  	// if both channels are ready, chan is selected randomly, therefore regardless of
   131  	// channel selected first, second one is also checked
   132  	stop := c.Stop(ctx)
   133  	done := ctx.Done()
   134  
   135  	select {
   136  	case <-done:
   137  		select {
   138  		case stopped := <-stop:
   139  			if !stopped {
   140  				return ctx.Err()
   141  			}
   142  		default:
   143  			return ctx.Err()
   144  		}
   145  	case stopped := <-stop:
   146  		if !stopped {
   147  			if ctx.Err() != nil {
   148  				return ctx.Err()
   149  			}
   150  			return fmt.Errorf("failed to stop cycle")
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  func (c *cycleManager) Running() bool {
   157  	c.RLock()
   158  	defer c.RUnlock()
   159  
   160  	return c.running
   161  }
   162  
   163  func (c *cycleManager) shouldStop() bool {
   164  	for _, ctx := range c.stopContexts {
   165  		if ctx.Err() == nil {
   166  			return true
   167  		}
   168  	}
   169  	return false
   170  }
   171  
   172  func (c *cycleManager) shouldAbortCycleCallback() bool {
   173  	c.RLock()
   174  	defer c.RUnlock()
   175  
   176  	return c.shouldStop()
   177  }
   178  
   179  func (c *cycleManager) isStopRequested() bool {
   180  	select {
   181  	case <-c.stopSignal:
   182  	case <-c.cycleTicker.C():
   183  		// as stop chan has higher priority,
   184  		// it is checked again in case of ticker was selected over stop if both were ready
   185  		select {
   186  		case <-c.stopSignal:
   187  		default:
   188  			return false
   189  		}
   190  	}
   191  	return true
   192  }
   193  
   194  func (c *cycleManager) handleStopRequest(stopped bool) {
   195  	for _, stopResult := range c.stopResults {
   196  		stopResult <- stopped
   197  		close(stopResult)
   198  	}
   199  	c.running = !stopped
   200  	c.stopContexts = nil
   201  	c.stopResults = nil
   202  }
   203  
   204  func NewManagerNoop() CycleManager {
   205  	return &cycleManagerNoop{running: false}
   206  }
   207  
   208  type cycleManagerNoop struct {
   209  	running bool
   210  }
   211  
   212  func (c *cycleManagerNoop) Start() {
   213  	c.running = true
   214  }
   215  
   216  func (c *cycleManagerNoop) Stop(ctx context.Context) chan bool {
   217  	if !c.running {
   218  		return c.closedChan(true)
   219  	}
   220  	if ctx.Err() != nil {
   221  		return c.closedChan(false)
   222  	}
   223  
   224  	c.running = false
   225  	return c.closedChan(true)
   226  }
   227  
   228  func (c *cycleManagerNoop) StopAndWait(ctx context.Context) error {
   229  	if <-c.Stop(ctx) {
   230  		return nil
   231  	}
   232  	return ctx.Err()
   233  }
   234  
   235  func (c *cycleManagerNoop) Running() bool {
   236  	return c.running
   237  }
   238  
   239  func (c *cycleManagerNoop) closedChan(val bool) chan bool {
   240  	ch := make(chan bool, 1)
   241  	ch <- val
   242  	close(ch)
   243  	return ch
   244  }