dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/client/pubsub/pubsub.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 implements a utility type to maintain resource watchers and
    25  // the updates.
    26  //
    27  // This package is designed to work with the xds resources. It could be made a
    28  // general system that works with all types.
    29  package pubsub
    30  
    31  import (
    32  	"sync"
    33  	"time"
    34  )
    35  
    36  import (
    37  	dubbogoLogger "github.com/dubbogo/gost/log/logger"
    38  )
    39  
    40  import (
    41  	"dubbo.apache.org/dubbo-go/v3/xds/client/resource"
    42  	"dubbo.apache.org/dubbo-go/v3/xds/utils/buffer"
    43  	"dubbo.apache.org/dubbo-go/v3/xds/utils/grpcsync"
    44  )
    45  
    46  // Pubsub maintains resource watchers and resource updates.
    47  //
    48  // There can be multiple watchers for the same resource. An update to a resource
    49  // triggers updates to all the existing watchers. Watchers can be canceled at
    50  // any time.
    51  type Pubsub struct {
    52  	done               *grpcsync.Event
    53  	logger             dubbogoLogger.Logger
    54  	watchExpiryTimeout time.Duration
    55  
    56  	updateCh *buffer.Unbounded // chan *watcherInfoWithUpdate
    57  	// All the following maps are to keep the updates/metadata in a cache.
    58  	mu          sync.Mutex
    59  	ldsWatchers map[string]map[*watchInfo]bool
    60  	ldsCache    map[string]resource.ListenerUpdate
    61  	ldsMD       map[string]resource.UpdateMetadata
    62  	rdsWatchers map[string]map[*watchInfo]bool
    63  	rdsCache    map[string]resource.RouteConfigUpdate
    64  	rdsMD       map[string]resource.UpdateMetadata
    65  	cdsWatchers map[string]map[*watchInfo]bool
    66  	cdsCache    map[string]resource.ClusterUpdate
    67  	cdsMD       map[string]resource.UpdateMetadata
    68  	edsWatchers map[string]map[*watchInfo]bool
    69  	edsCache    map[string]resource.EndpointsUpdate
    70  	edsMD       map[string]resource.UpdateMetadata
    71  }
    72  
    73  // New creates a new Pubsub.
    74  func New(watchExpiryTimeout time.Duration, logger dubbogoLogger.Logger) *Pubsub {
    75  	pb := &Pubsub{
    76  		done:               grpcsync.NewEvent(),
    77  		logger:             logger,
    78  		watchExpiryTimeout: watchExpiryTimeout,
    79  
    80  		updateCh:    buffer.NewUnbounded(),
    81  		ldsWatchers: make(map[string]map[*watchInfo]bool),
    82  		ldsCache:    make(map[string]resource.ListenerUpdate),
    83  		ldsMD:       make(map[string]resource.UpdateMetadata),
    84  		rdsWatchers: make(map[string]map[*watchInfo]bool),
    85  		rdsCache:    make(map[string]resource.RouteConfigUpdate),
    86  		rdsMD:       make(map[string]resource.UpdateMetadata),
    87  		cdsWatchers: make(map[string]map[*watchInfo]bool),
    88  		cdsCache:    make(map[string]resource.ClusterUpdate),
    89  		cdsMD:       make(map[string]resource.UpdateMetadata),
    90  		edsWatchers: make(map[string]map[*watchInfo]bool),
    91  		edsCache:    make(map[string]resource.EndpointsUpdate),
    92  		edsMD:       make(map[string]resource.UpdateMetadata),
    93  	}
    94  	go pb.run()
    95  	return pb
    96  }
    97  
    98  // WatchListener registers a watcher for the LDS resource.
    99  //
   100  // It also returns whether this is the first watch for this resource.
   101  func (pb *Pubsub) WatchListener(serviceName string, cb func(resource.ListenerUpdate, error)) (first bool, cancel func() bool) {
   102  	wi := &watchInfo{
   103  		c:           pb,
   104  		rType:       resource.ListenerResource,
   105  		target:      serviceName,
   106  		ldsCallback: cb,
   107  	}
   108  
   109  	wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() {
   110  		wi.timeout()
   111  	})
   112  	return pb.watch(wi)
   113  }
   114  
   115  // WatchRouteConfig register a watcher for the RDS resource.
   116  //
   117  // It also returns whether this is the first watch for this resource.
   118  func (pb *Pubsub) WatchRouteConfig(routeName string, cb func(resource.RouteConfigUpdate, error)) (first bool, cancel func() bool) {
   119  	wi := &watchInfo{
   120  		c:           pb,
   121  		rType:       resource.RouteConfigResource,
   122  		target:      routeName,
   123  		rdsCallback: cb,
   124  	}
   125  
   126  	wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() {
   127  		wi.timeout()
   128  	})
   129  	return pb.watch(wi)
   130  }
   131  
   132  // WatchCluster register a watcher for the CDS resource.
   133  //
   134  // It also returns whether this is the first watch for this resource.
   135  func (pb *Pubsub) WatchCluster(clusterName string, cb func(resource.ClusterUpdate, error)) (first bool, cancel func() bool) {
   136  	wi := &watchInfo{
   137  		c:           pb,
   138  		rType:       resource.ClusterResource,
   139  		target:      clusterName,
   140  		cdsCallback: cb,
   141  	}
   142  
   143  	wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() {
   144  		wi.timeout()
   145  	})
   146  	return pb.watch(wi)
   147  }
   148  
   149  // WatchEndpoints registers a watcher for the EDS resource.
   150  //
   151  // It also returns whether this is the first watch for this resource.
   152  func (pb *Pubsub) WatchEndpoints(clusterName string, cb func(resource.EndpointsUpdate, error)) (first bool, cancel func() bool) {
   153  	wi := &watchInfo{
   154  		c:           pb,
   155  		rType:       resource.EndpointsResource,
   156  		target:      clusterName,
   157  		edsCallback: cb,
   158  	}
   159  
   160  	wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() {
   161  		wi.timeout()
   162  	})
   163  	return pb.watch(wi)
   164  }
   165  
   166  // Close closes the pubsub.
   167  func (pb *Pubsub) Close() {
   168  	if pb.done.HasFired() {
   169  		return
   170  	}
   171  	pb.done.Fire()
   172  }
   173  
   174  // run is a goroutine for all the callbacks.
   175  //
   176  // Callback can be called in watch(), if an item is found in cache. Without this
   177  // goroutine, the callback will be called inline, which might cause a deadlock
   178  // in user's code. Callbacks also cannot be simple `go callback()` because the
   179  // order matters.
   180  func (pb *Pubsub) run() {
   181  	for {
   182  		select {
   183  		case t := <-pb.updateCh.Get():
   184  			pb.updateCh.Load()
   185  			if pb.done.HasFired() {
   186  				return
   187  			}
   188  			pb.callCallback(t.(*watcherInfoWithUpdate))
   189  		case <-pb.done.Done():
   190  			return
   191  		}
   192  	}
   193  }