vitess.io/vitess@v0.16.2/go/vt/topo/etcd2topo/watch.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package etcd2topo 18 19 import ( 20 "context" 21 "path" 22 "strings" 23 "time" 24 25 "go.etcd.io/etcd/api/v3/mvccpb" 26 clientv3 "go.etcd.io/etcd/client/v3" 27 28 "vitess.io/vitess/go/vt/proto/vtrpc" 29 "vitess.io/vitess/go/vt/vterrors" 30 31 "vitess.io/vitess/go/vt/log" 32 "vitess.io/vitess/go/vt/topo" 33 ) 34 35 // Watch is part of the topo.Conn interface. 36 func (s *Server) Watch(ctx context.Context, filePath string) (*topo.WatchData, <-chan *topo.WatchData, error) { 37 nodePath := path.Join(s.root, filePath) 38 39 // Get the initial version of the file 40 initialCtx, initialCancel := context.WithTimeout(ctx, topo.RemoteOperationTimeout) 41 defer initialCancel() 42 initial, err := s.cli.Get(initialCtx, nodePath) 43 if err != nil { 44 // Generic error. 45 return nil, nil, convertError(err, nodePath) 46 } 47 48 if len(initial.Kvs) != 1 { 49 // Node doesn't exist. 50 return nil, nil, topo.NewError(topo.NoNode, nodePath) 51 } 52 wd := &topo.WatchData{ 53 Contents: initial.Kvs[0].Value, 54 Version: EtcdVersion(initial.Kvs[0].ModRevision), 55 } 56 57 // Create an outer context that will be canceled on return and will cancel all inner watches. 58 outerCtx, outerCancel := context.WithCancel(ctx) 59 60 // Create a context, will be used to cancel the watch on retry. 61 watchCtx, watchCancel := context.WithCancel(outerCtx) 62 63 // Create the Watcher. We start watching from the response we 64 // got, not from the file original version, as the server may 65 // not have that much history. 66 watcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(initial.Header.Revision)) 67 if watcher == nil { 68 watchCancel() 69 outerCancel() 70 return nil, nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "Watch failed") 71 } 72 73 // Create the notifications channel, send updates to it. 74 notifications := make(chan *topo.WatchData, 10) 75 go func() { 76 defer close(notifications) 77 defer outerCancel() 78 79 var currVersion = initial.Header.Revision 80 var watchRetries int 81 for { 82 select { 83 case <-s.running: 84 return 85 case <-watchCtx.Done(): 86 // This includes context cancellation errors. 87 notifications <- &topo.WatchData{ 88 Err: convertError(watchCtx.Err(), nodePath), 89 } 90 return 91 case wresp, ok := <-watcher: 92 if !ok { 93 if watchRetries > 10 { 94 t := time.NewTimer(time.Duration(watchRetries) * time.Second) 95 select { 96 case <-t.C: 97 t.Stop() 98 case <-s.running: 99 t.Stop() 100 continue 101 case <-watchCtx.Done(): 102 t.Stop() 103 continue 104 } 105 } 106 watchRetries++ 107 // Cancel inner context on retry and create new one. 108 watchCancel() 109 watchCtx, watchCancel = context.WithCancel(ctx) 110 newWatcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(currVersion)) 111 if newWatcher == nil { 112 log.Warningf("watch %v failed and get a nil channel returned, currVersion: %v", nodePath, currVersion) 113 } else { 114 watcher = newWatcher 115 } 116 continue 117 } 118 119 watchRetries = 0 120 121 if wresp.Canceled { 122 // Final notification. 123 notifications <- &topo.WatchData{ 124 Err: convertError(wresp.Err(), nodePath), 125 } 126 return 127 } 128 129 currVersion = wresp.Header.GetRevision() 130 131 for _, ev := range wresp.Events { 132 switch ev.Type { 133 case mvccpb.PUT: 134 notifications <- &topo.WatchData{ 135 Contents: ev.Kv.Value, 136 Version: EtcdVersion(ev.Kv.Version), 137 } 138 case mvccpb.DELETE: 139 // Node is gone, send a final notice. 140 notifications <- &topo.WatchData{ 141 Err: topo.NewError(topo.NoNode, nodePath), 142 } 143 return 144 default: 145 notifications <- &topo.WatchData{ 146 Err: vterrors.Errorf(vtrpc.Code_INTERNAL, "unexpected event received: %v", ev), 147 } 148 return 149 } 150 } 151 } 152 } 153 }() 154 155 return wd, notifications, nil 156 } 157 158 // WatchRecursive is part of the topo.Conn interface. 159 func (s *Server) WatchRecursive(ctx context.Context, dirpath string) ([]*topo.WatchDataRecursive, <-chan *topo.WatchDataRecursive, error) { 160 nodePath := path.Join(s.root, dirpath) 161 if !strings.HasSuffix(nodePath, "/") { 162 nodePath = nodePath + "/" 163 } 164 165 // Get the initial version of the file 166 initial, err := s.cli.Get(ctx, nodePath, clientv3.WithPrefix()) 167 if err != nil { 168 return nil, nil, convertError(err, nodePath) 169 } 170 171 var initialwd []*topo.WatchDataRecursive 172 173 for _, kv := range initial.Kvs { 174 var wd topo.WatchDataRecursive 175 wd.Path = string(kv.Key) 176 wd.Contents = kv.Value 177 wd.Version = EtcdVersion(initial.Kvs[0].ModRevision) 178 initialwd = append(initialwd, &wd) 179 } 180 181 // Create an outer context that will be canceled on return and will cancel all inner watches. 182 outerCtx, outerCancel := context.WithCancel(ctx) 183 184 // Create a context, will be used to cancel the watch on retry. 185 watchCtx, watchCancel := context.WithCancel(outerCtx) 186 187 // Create the Watcher. We start watching from the response we 188 // got, not from the file original version, as the server may 189 // not have that much history. 190 watcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(initial.Header.Revision), clientv3.WithPrefix()) 191 if watcher == nil { 192 watchCancel() 193 outerCancel() 194 return nil, nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "Watch failed") 195 } 196 197 // Create the notifications channel, send updates to it. 198 notifications := make(chan *topo.WatchDataRecursive, 10) 199 go func() { 200 defer close(notifications) 201 defer outerCancel() 202 203 var currVersion = initial.Header.Revision 204 var watchRetries int 205 for { 206 select { 207 case <-s.running: 208 return 209 case <-watchCtx.Done(): 210 // This includes context cancellation errors. 211 notifications <- &topo.WatchDataRecursive{ 212 WatchData: topo.WatchData{Err: convertError(watchCtx.Err(), nodePath)}, 213 } 214 return 215 case wresp, ok := <-watcher: 216 if !ok { 217 if watchRetries > 10 { 218 select { 219 case <-time.After(time.Duration(watchRetries) * time.Second): 220 case <-s.running: 221 continue 222 case <-watchCtx.Done(): 223 continue 224 } 225 } 226 watchRetries++ 227 // Cancel inner context on retry and create new one. 228 watchCancel() 229 watchCtx, watchCancel = context.WithCancel(ctx) 230 231 newWatcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(currVersion), clientv3.WithPrefix()) 232 if newWatcher == nil { 233 log.Warningf("watch %v failed and get a nil channel returned, currVersion: %v", nodePath, currVersion) 234 } else { 235 watcher = newWatcher 236 } 237 continue 238 } 239 240 watchRetries = 0 241 242 if wresp.Canceled { 243 // Final notification. 244 notifications <- &topo.WatchDataRecursive{ 245 WatchData: topo.WatchData{Err: convertError(wresp.Err(), nodePath)}, 246 } 247 return 248 } 249 250 currVersion = wresp.Header.GetRevision() 251 252 for _, ev := range wresp.Events { 253 switch ev.Type { 254 case mvccpb.PUT: 255 notifications <- &topo.WatchDataRecursive{ 256 Path: string(ev.Kv.Key), 257 WatchData: topo.WatchData{ 258 Contents: ev.Kv.Value, 259 Version: EtcdVersion(ev.Kv.Version), 260 }, 261 } 262 case mvccpb.DELETE: 263 notifications <- &topo.WatchDataRecursive{ 264 Path: string(ev.Kv.Key), 265 WatchData: topo.WatchData{ 266 Err: topo.NewError(topo.NoNode, nodePath), 267 }, 268 } 269 } 270 } 271 } 272 } 273 }() 274 275 return initialwd, notifications, nil 276 }