github.com/outbrain/consul@v1.4.5/watch/plan.go (about)

     1  package watch
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"reflect"
     9  	"time"
    10  
    11  	consulapi "github.com/hashicorp/consul/api"
    12  )
    13  
    14  const (
    15  	// retryInterval is the base retry value
    16  	retryInterval = 5 * time.Second
    17  
    18  	// maximum back off time, this is to prevent
    19  	// exponential runaway
    20  	maxBackoffTime = 180 * time.Second
    21  )
    22  
    23  func (p *Plan) Run(address string) error {
    24  	return p.RunWithConfig(address, nil)
    25  }
    26  
    27  // Run is used to run a watch plan
    28  func (p *Plan) RunWithConfig(address string, conf *consulapi.Config) error {
    29  	// Setup the client
    30  	p.address = address
    31  	if conf == nil {
    32  		conf = consulapi.DefaultConfig()
    33  	}
    34  	conf.Address = address
    35  	conf.Datacenter = p.Datacenter
    36  	conf.Token = p.Token
    37  	client, err := consulapi.NewClient(conf)
    38  	if err != nil {
    39  		return fmt.Errorf("Failed to connect to agent: %v", err)
    40  	}
    41  
    42  	// Create the logger
    43  	output := p.LogOutput
    44  	if output == nil {
    45  		output = os.Stderr
    46  	}
    47  	logger := log.New(output, "", log.LstdFlags)
    48  
    49  	return p.RunWithClientAndLogger(client, logger)
    50  }
    51  
    52  // RunWithClientAndLogger runs a watch plan using an external client and
    53  // log.Logger instance. Using this, the plan's Datacenter, Token and LogOutput
    54  // fields are ignored and the passed client is expected to be configured as
    55  // needed.
    56  func (p *Plan) RunWithClientAndLogger(client *consulapi.Client,
    57  	logger *log.Logger) error {
    58  
    59  	p.client = client
    60  
    61  	// Loop until we are canceled
    62  	failures := 0
    63  OUTER:
    64  	for !p.shouldStop() {
    65  		// Invoke the handler
    66  		blockParamVal, result, err := p.Watcher(p)
    67  
    68  		// Check if we should terminate since the function
    69  		// could have blocked for a while
    70  		if p.shouldStop() {
    71  			break
    72  		}
    73  
    74  		// Handle an error in the watch function
    75  		if err != nil {
    76  			// Perform an exponential backoff
    77  			failures++
    78  			if blockParamVal == nil {
    79  				p.lastParamVal = nil
    80  			} else {
    81  				p.lastParamVal = blockParamVal.Next(p.lastParamVal)
    82  			}
    83  			retry := retryInterval * time.Duration(failures*failures)
    84  			if retry > maxBackoffTime {
    85  				retry = maxBackoffTime
    86  			}
    87  			logger.Printf("[ERR] consul.watch: Watch (type: %s) errored: %v, retry in %v",
    88  				p.Type, err, retry)
    89  			select {
    90  			case <-time.After(retry):
    91  				continue OUTER
    92  			case <-p.stopCh:
    93  				return nil
    94  			}
    95  		}
    96  
    97  		// Clear the failures
    98  		failures = 0
    99  
   100  		// If the index is unchanged do nothing
   101  		if p.lastParamVal != nil && p.lastParamVal.Equal(blockParamVal) {
   102  			continue
   103  		}
   104  
   105  		// Update the index, look for change
   106  		oldParamVal := p.lastParamVal
   107  		p.lastParamVal = blockParamVal.Next(oldParamVal)
   108  		if oldParamVal != nil && reflect.DeepEqual(p.lastResult, result) {
   109  			continue
   110  		}
   111  
   112  		// Handle the updated result
   113  		p.lastResult = result
   114  		// If a hybrid handler exists use that
   115  		if p.HybridHandler != nil {
   116  			p.HybridHandler(blockParamVal, result)
   117  		} else if p.Handler != nil {
   118  			idx, ok := blockParamVal.(WaitIndexVal)
   119  			if !ok {
   120  				logger.Printf("[ERR] consul.watch: Handler only supports index-based " +
   121  					" watches but non index-based watch run. Skipping Handler.")
   122  			}
   123  			p.Handler(uint64(idx), result)
   124  		}
   125  	}
   126  	return nil
   127  }
   128  
   129  // Stop is used to stop running the watch plan
   130  func (p *Plan) Stop() {
   131  	p.stopLock.Lock()
   132  	defer p.stopLock.Unlock()
   133  	if p.stop {
   134  		return
   135  	}
   136  	p.stop = true
   137  	if p.cancelFunc != nil {
   138  		p.cancelFunc()
   139  	}
   140  	close(p.stopCh)
   141  }
   142  
   143  func (p *Plan) shouldStop() bool {
   144  	select {
   145  	case <-p.stopCh:
   146  		return true
   147  	default:
   148  		return false
   149  	}
   150  }
   151  
   152  func (p *Plan) setCancelFunc(cancel context.CancelFunc) {
   153  	p.stopLock.Lock()
   154  	defer p.stopLock.Unlock()
   155  	if p.shouldStop() {
   156  		// The watch is stopped and execute the new cancel func to stop watchFactory
   157  		cancel()
   158  		return
   159  	}
   160  	p.cancelFunc = cancel
   161  }
   162  
   163  func (p *Plan) IsStopped() bool {
   164  	p.stopLock.Lock()
   165  	defer p.stopLock.Unlock()
   166  	return p.stop
   167  }