github.com/m3db/m3@v1.5.0/src/dbnode/namespace/namespace_watch.go (about)

     1  // Copyright (c) 2017 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 namespace
    22  
    23  import (
    24  	"errors"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/x/instrument"
    29  
    30  	"github.com/uber-go/tally"
    31  	"go.uber.org/zap"
    32  )
    33  
    34  var (
    35  	errAlreadyWatching = errors.New("database is already watching namespace updates")
    36  	errNotWatching     = errors.New("database is not watching for namespace updates")
    37  )
    38  
    39  type dbNamespaceWatch struct {
    40  	sync.Mutex
    41  
    42  	watching bool
    43  	watch    Watch
    44  	doneCh   chan struct{}
    45  	closedCh chan struct{}
    46  
    47  	update NamespaceUpdater
    48  
    49  	log            *zap.Logger
    50  	metrics        dbNamespaceWatchMetrics
    51  	reportInterval time.Duration
    52  }
    53  
    54  type dbNamespaceWatchMetrics struct {
    55  	activeNamespaces tally.Gauge
    56  	updates          tally.Counter
    57  }
    58  
    59  func newWatchMetrics(
    60  	scope tally.Scope,
    61  ) dbNamespaceWatchMetrics {
    62  	nsScope := scope.SubScope("namespace-watch")
    63  	return dbNamespaceWatchMetrics{
    64  		activeNamespaces: nsScope.Gauge("active"),
    65  		updates:          nsScope.Counter("updates"),
    66  	}
    67  }
    68  
    69  func NewNamespaceWatch(
    70  	update NamespaceUpdater,
    71  	w Watch,
    72  	iopts instrument.Options,
    73  ) NamespaceWatch {
    74  	scope := iopts.MetricsScope()
    75  	return &dbNamespaceWatch{
    76  		watch:          w,
    77  		update:         update,
    78  		log:            iopts.Logger(),
    79  		metrics:        newWatchMetrics(scope),
    80  		reportInterval: iopts.ReportInterval(),
    81  	}
    82  }
    83  
    84  func (w *dbNamespaceWatch) Start() error {
    85  	w.Lock()
    86  	defer w.Unlock()
    87  
    88  	if w.watching {
    89  		return errAlreadyWatching
    90  	}
    91  
    92  	w.watching = true
    93  	w.doneCh = make(chan struct{}, 1)
    94  	w.closedCh = make(chan struct{}, 1)
    95  
    96  	go w.startWatch()
    97  
    98  	return nil
    99  }
   100  
   101  func (w *dbNamespaceWatch) startWatch() {
   102  	reportClosingCh := make(chan struct{}, 1)
   103  	reportClosedCh := make(chan struct{}, 1)
   104  	go func() {
   105  		ticker := time.NewTicker(w.reportInterval)
   106  		defer func() {
   107  			ticker.Stop()
   108  			close(reportClosedCh)
   109  		}()
   110  		for {
   111  			select {
   112  			case <-ticker.C:
   113  				w.reportMetrics()
   114  			case <-reportClosingCh:
   115  				return
   116  			}
   117  		}
   118  	}()
   119  
   120  	defer func() {
   121  		// Issue closing signal to report channel
   122  		close(reportClosingCh)
   123  		// Wait for report channel to close
   124  		<-reportClosedCh
   125  		// Signal all closed
   126  		close(w.closedCh)
   127  	}()
   128  
   129  	for {
   130  		select {
   131  		case <-w.doneCh:
   132  			return
   133  		case _, ok := <-w.watch.C():
   134  			if !ok {
   135  				return
   136  			}
   137  
   138  			if !w.isWatching() {
   139  				return
   140  			}
   141  
   142  			w.metrics.updates.Inc(1)
   143  			newMap := w.watch.Get()
   144  			w.log.Info("received update from kv namespace watch")
   145  			if err := w.update(newMap); err != nil {
   146  				w.log.Error("failed to update owned namespaces",
   147  					zap.Error(err))
   148  			}
   149  		}
   150  	}
   151  }
   152  
   153  func (w *dbNamespaceWatch) isWatching() bool {
   154  	w.Lock()
   155  	defer w.Unlock()
   156  	return w.watching
   157  }
   158  
   159  func (w *dbNamespaceWatch) reportMetrics() {
   160  	m := w.watch.Get()
   161  	if m == nil {
   162  		w.metrics.activeNamespaces.Update(0)
   163  		return
   164  	}
   165  	w.metrics.activeNamespaces.Update(float64(len(m.Metadatas())))
   166  }
   167  
   168  func (w *dbNamespaceWatch) Stop() error {
   169  	w.Lock()
   170  	defer w.Unlock()
   171  
   172  	if !w.watching {
   173  		return errNotWatching
   174  	}
   175  
   176  	w.watching = false
   177  	close(w.doneCh)
   178  	<-w.closedCh
   179  
   180  	return nil
   181  }
   182  
   183  func (w *dbNamespaceWatch) Close() error {
   184  	w.Lock()
   185  	watching := w.watching
   186  	w.Unlock()
   187  	if watching {
   188  		return w.Stop()
   189  	}
   190  	return nil
   191  }