github.com/kjdelisle/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 }