github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/xdsclient/pubsub/pubsub.go (about)

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