github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/watch/value.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package watch
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  	"time"
    28  
    29  	"go.uber.org/zap"
    30  
    31  	xos "github.com/m3db/m3/src/x/os"
    32  )
    33  
    34  var (
    35  	errInitWatchTimeout = errors.New("init watch timeout")
    36  	errNilValue         = errors.New("nil kv value")
    37  )
    38  
    39  // Value is a resource that can be updated during runtime.
    40  type Value interface {
    41  	// Watch starts watching for value updates.
    42  	Watch() error
    43  
    44  	// Unwatch stops watching for value updates.
    45  	Unwatch()
    46  }
    47  
    48  // NewUpdatableFn creates an updatable.
    49  type NewUpdatableFn func() (Updatable, error)
    50  
    51  // GetUpdateFn returns the latest value.
    52  type GetUpdateFn func(updatable Updatable) (interface{}, error)
    53  
    54  // ProcessFn processes an update.
    55  type ProcessFn func(update interface{}) error
    56  
    57  // processWithLockFn updates a value while holding a lock.
    58  type processWithLockFn func(update interface{}) error
    59  
    60  type valueStatus int
    61  
    62  const (
    63  	valueNotWatching valueStatus = iota
    64  	valueWatching
    65  )
    66  
    67  type value struct {
    68  	sync.RWMutex
    69  
    70  	opts              Options
    71  	log               *zap.Logger
    72  	newUpdatableFn    NewUpdatableFn
    73  	getUpdateFn       GetUpdateFn
    74  	processFn         ProcessFn
    75  	processWithLockFn processWithLockFn
    76  	key               string
    77  
    78  	updatable Updatable
    79  	status    valueStatus
    80  }
    81  
    82  // NewValue creates a new value.
    83  func NewValue(
    84  	opts Options,
    85  ) Value {
    86  	v := &value{
    87  		opts:           opts,
    88  		log:            opts.InstrumentOptions().Logger(),
    89  		newUpdatableFn: opts.NewUpdatableFn(),
    90  		getUpdateFn:    opts.GetUpdateFn(),
    91  		processFn:      opts.ProcessFn(),
    92  	}
    93  	v.processWithLockFn = v.processWithLock
    94  	return v
    95  }
    96  
    97  func (v *value) Watch() error {
    98  	v.Lock()
    99  	defer v.Unlock()
   100  
   101  	if v.status == valueWatching {
   102  		return nil
   103  	}
   104  	updatable, err := v.newUpdatableFn()
   105  	if err != nil {
   106  		return CreateWatchError{
   107  			innerError: err,
   108  			key:        v.opts.Key(),
   109  		}
   110  	}
   111  	v.status = valueWatching
   112  	v.updatable = updatable
   113  	// NB(xichen): we want to start watching updates even though
   114  	// we may fail to initialize the value temporarily (e.g., during
   115  	// a network partition) so the value will be updated when the
   116  	// error condition is resolved.
   117  	defer func() { go v.watchUpdates(v.updatable) }()
   118  
   119  	interruptedCh := v.opts.InterruptedCh()
   120  	if interruptedCh == nil {
   121  		// NB(nate): if no interrupted channel is provided, then this wait is not
   122  		// gracefully interruptable.
   123  		interruptedCh = make(chan struct{})
   124  	}
   125  
   126  	select {
   127  	case <-v.updatable.C():
   128  	case <-time.After(v.opts.InitWatchTimeout()):
   129  		return InitValueError{
   130  			innerError: errInitWatchTimeout,
   131  			key:        v.opts.Key(),
   132  		}
   133  	case <-interruptedCh:
   134  		return xos.ErrInterrupted
   135  	}
   136  
   137  	update, err := v.getUpdateFn(v.updatable)
   138  	if err != nil {
   139  		return InitValueError{
   140  			innerError: err,
   141  			key:        v.opts.Key(),
   142  		}
   143  	}
   144  
   145  	if err = v.processWithLockFn(update); err != nil {
   146  		return InitValueError{
   147  			innerError: err,
   148  			key:        v.opts.Key(),
   149  		}
   150  	}
   151  	return nil
   152  }
   153  
   154  func (v *value) Unwatch() {
   155  	v.Lock()
   156  	defer v.Unlock()
   157  
   158  	// If status is nil, it means we are not watching.
   159  	if v.status == valueNotWatching {
   160  		return
   161  	}
   162  	v.updatable.Close()
   163  	v.status = valueNotWatching
   164  	v.updatable = nil
   165  }
   166  
   167  func (v *value) watchUpdates(updatable Updatable) {
   168  	for range updatable.C() {
   169  		v.Lock()
   170  		// If we are not watching, or we are watching with a different
   171  		// watch because we stopped the current watch and started a new
   172  		// one, return immediately.
   173  		if v.status != valueWatching || v.updatable != updatable {
   174  			v.Unlock()
   175  			return
   176  		}
   177  		update, err := v.getUpdateFn(updatable)
   178  		if err != nil {
   179  			v.log.Error("error getting update",
   180  				zap.String("key", v.opts.Key()),
   181  				zap.Error(err))
   182  			v.Unlock()
   183  			continue
   184  		}
   185  		if err = v.processWithLockFn(update); err != nil {
   186  			v.log.Error("error applying update",
   187  				zap.String("key", v.opts.Key()),
   188  				zap.Error(err))
   189  		}
   190  		v.Unlock()
   191  	}
   192  }
   193  
   194  func (v *value) processWithLock(update interface{}) error {
   195  	if update == nil {
   196  		return errNilValue
   197  	}
   198  	return v.processFn(update)
   199  }
   200  
   201  // CreateWatchError is returned when encountering an error creating a watch.
   202  type CreateWatchError struct {
   203  	innerError error
   204  	key        string
   205  }
   206  
   207  func (e CreateWatchError) Error() string {
   208  	return fmt.Sprintf("create watch error (key='%s'): %v", e.key, e.innerError)
   209  }
   210  
   211  // InitValueError is returned when encountering an error when initializing a value.
   212  type InitValueError struct {
   213  	innerError error
   214  	key        string
   215  }
   216  
   217  func (e InitValueError) Error() string {
   218  	return fmt.Sprintf("initializing value error (key='%s'): %v", e.key, e.innerError)
   219  }