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  }