dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/client/pubsub/watch.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2021 gRPC authors. 21 * 22 */ 23 24 package pubsub 25 26 import ( 27 "fmt" 28 "sync" 29 "time" 30 ) 31 32 import ( 33 "dubbo.apache.org/dubbo-go/v3/xds/client/resource" 34 "dubbo.apache.org/dubbo-go/v3/xds/utils/pretty" 35 ) 36 37 type watchInfoState int 38 39 const ( 40 watchInfoStateStarted watchInfoState = iota 41 watchInfoStateRespReceived 42 watchInfoStateTimeout 43 watchInfoStateCanceled 44 ) 45 46 // watchInfo holds all the information from a watch() call. 47 type watchInfo struct { 48 c *Pubsub 49 rType resource.ResourceType 50 target string 51 52 ldsCallback func(resource.ListenerUpdate, error) 53 rdsCallback func(resource.RouteConfigUpdate, error) 54 cdsCallback func(resource.ClusterUpdate, error) 55 edsCallback func(resource.EndpointsUpdate, error) 56 57 expiryTimer *time.Timer 58 59 // mu protects state, and c.scheduleCallback(). 60 // - No callback should be scheduled after watchInfo is canceled. 61 // - No timeout error should be scheduled after watchInfo is resp received. 62 mu sync.Mutex 63 state watchInfoState 64 } 65 66 func (wi *watchInfo) newUpdate(update interface{}) { 67 wi.mu.Lock() 68 defer wi.mu.Unlock() 69 if wi.state == watchInfoStateCanceled { 70 return 71 } 72 wi.state = watchInfoStateRespReceived 73 wi.expiryTimer.Stop() 74 wi.c.scheduleCallback(wi, update, nil) 75 } 76 77 func (wi *watchInfo) newError(err error) { 78 wi.mu.Lock() 79 defer wi.mu.Unlock() 80 if wi.state == watchInfoStateCanceled { 81 return 82 } 83 wi.state = watchInfoStateRespReceived 84 wi.expiryTimer.Stop() 85 wi.sendErrorLocked(err) 86 } 87 88 func (wi *watchInfo) resourceNotFound() { 89 wi.mu.Lock() 90 defer wi.mu.Unlock() 91 if wi.state == watchInfoStateCanceled { 92 return 93 } 94 wi.state = watchInfoStateRespReceived 95 wi.expiryTimer.Stop() 96 wi.sendErrorLocked(resource.NewErrorf(resource.ErrorTypeResourceNotFound, "xds: %v target %s not found in received response", wi.rType, wi.target)) 97 } 98 99 func (wi *watchInfo) timeout() { 100 wi.mu.Lock() 101 defer wi.mu.Unlock() 102 if wi.state == watchInfoStateCanceled || wi.state == watchInfoStateRespReceived { 103 return 104 } 105 wi.state = watchInfoStateTimeout 106 wi.sendErrorLocked(fmt.Errorf("xds: %v target %s not found, watcher timeout", wi.rType, wi.target)) 107 } 108 109 // Caller must hold wi.mu. 110 func (wi *watchInfo) sendErrorLocked(err error) { 111 var ( 112 u interface{} 113 ) 114 switch wi.rType { 115 case resource.ListenerResource: 116 u = resource.ListenerUpdate{} 117 case resource.RouteConfigResource: 118 u = resource.RouteConfigUpdate{} 119 case resource.ClusterResource: 120 u = resource.ClusterUpdate{} 121 case resource.EndpointsResource: 122 u = resource.EndpointsUpdate{} 123 } 124 wi.c.scheduleCallback(wi, u, err) 125 } 126 127 func (wi *watchInfo) cancel() { 128 wi.mu.Lock() 129 defer wi.mu.Unlock() 130 if wi.state == watchInfoStateCanceled { 131 return 132 } 133 wi.expiryTimer.Stop() 134 wi.state = watchInfoStateCanceled 135 } 136 137 func (pb *Pubsub) watch(wi *watchInfo) (first bool, cancel func() bool) { 138 pb.mu.Lock() 139 defer pb.mu.Unlock() 140 pb.logger.Debugf("new watch for type %v, resource name %v", wi.rType, wi.target) 141 var ( 142 watchers map[string]map[*watchInfo]bool 143 mds map[string]resource.UpdateMetadata 144 ) 145 switch wi.rType { 146 case resource.ListenerResource: 147 watchers = pb.ldsWatchers 148 mds = pb.ldsMD 149 case resource.RouteConfigResource: 150 watchers = pb.rdsWatchers 151 mds = pb.rdsMD 152 case resource.ClusterResource: 153 watchers = pb.cdsWatchers 154 mds = pb.cdsMD 155 case resource.EndpointsResource: 156 watchers = pb.edsWatchers 157 mds = pb.edsMD 158 default: 159 pb.logger.Errorf("unknown watch type: %v", wi.rType) 160 return false, nil 161 } 162 163 var firstWatcher bool 164 resourceName := wi.target 165 s, ok := watchers[wi.target] 166 if !ok { 167 // If this is a new watcher, will ask lower level to send a new request 168 // with the resource name. 169 // 170 // If this (type+name) is already being watched, will not notify the 171 // underlying versioned apiClient. 172 pb.logger.Debugf("first watch for type %v, resource name %v, will send a new xDS request", wi.rType, wi.target) 173 s = make(map[*watchInfo]bool) 174 watchers[resourceName] = s 175 mds[resourceName] = resource.UpdateMetadata{Status: resource.ServiceStatusRequested} 176 firstWatcher = true 177 } 178 // No matter what, add the new watcher to the set, so it's callback will be 179 // call for new responses. 180 s[wi] = true 181 182 // If the resource is in cache, call the callback with the value. 183 switch wi.rType { 184 case resource.ListenerResource: 185 if v, ok := pb.ldsCache[resourceName]; ok { 186 pb.logger.Debugf("LDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) 187 wi.newUpdate(v) 188 } 189 case resource.RouteConfigResource: 190 if v, ok := pb.rdsCache[resourceName]; ok { 191 pb.logger.Debugf("RDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) 192 wi.newUpdate(v) 193 } 194 case resource.ClusterResource: 195 if v, ok := pb.cdsCache["*"]; ok { 196 pb.logger.Debugf("CDS resource with name * found in cache: %+v", pretty.ToJSON(v)) 197 wi.newUpdate(v) 198 } 199 if v, ok := pb.cdsCache[resourceName]; ok { 200 pb.logger.Debugf("CDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) 201 wi.newUpdate(v) 202 } 203 case resource.EndpointsResource: 204 if v, ok := pb.edsCache[resourceName]; ok { 205 pb.logger.Debugf("EDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) 206 wi.newUpdate(v) 207 } 208 } 209 210 return firstWatcher, func() bool { 211 pb.logger.Debugf("watch for type %v, resource name %v canceled", wi.rType, wi.target) 212 wi.cancel() 213 pb.mu.Lock() 214 defer pb.mu.Unlock() 215 var lastWatcher bool 216 if s := watchers[resourceName]; s != nil { 217 // Remove this watcher, so it's callback will not be called in the 218 // future. 219 delete(s, wi) 220 if len(s) == 0 { 221 pb.logger.Debugf("last watch for type %v, resource name %v canceled, will send a new xDS request", wi.rType, wi.target) 222 // If this was the last watcher, also tell xdsv2Client to stop 223 // watching this resource. 224 delete(watchers, resourceName) 225 delete(mds, resourceName) 226 lastWatcher = true 227 // Remove the resource from cache. When a watch for this 228 // resource is added later, it will trigger a xDS request with 229 // resource names, and client will receive new xDS responses. 230 switch wi.rType { 231 case resource.ListenerResource: 232 delete(pb.ldsCache, resourceName) 233 case resource.RouteConfigResource: 234 delete(pb.rdsCache, resourceName) 235 case resource.ClusterResource: 236 delete(pb.cdsCache, resourceName) 237 case resource.EndpointsResource: 238 delete(pb.edsCache, resourceName) 239 } 240 } 241 } 242 return lastWatcher 243 } 244 }