gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/xds/internal/xdsclient/singleton.go (about)

     1  /*
     2   *
     3   * Copyright 2020 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  
    19  package xdsclient
    20  
    21  import (
    22  	"bytes"
    23  	"encoding/json"
    24  	"fmt"
    25  	"sync"
    26  	"time"
    27  
    28  	"gitee.com/ks-custle/core-gm/grpc/xds/internal/xdsclient/bootstrap"
    29  )
    30  
    31  const (
    32  	defaultWatchExpiryTimeout         = 15 * time.Second
    33  	defaultIdleAuthorityDeleteTimeout = 5 * time.Minute
    34  )
    35  
    36  // This is the Client returned by New(). It contains one client implementation,
    37  // and maintains the refcount.
    38  var singletonClient = &clientRefCounted{}
    39  
    40  // To override in tests.
    41  var bootstrapNewConfig = bootstrap.NewConfig
    42  
    43  // clientRefCounted is ref-counted, and to be shared by the xds resolver and
    44  // balancer implementations, across multiple ClientConns and Servers.
    45  type clientRefCounted struct {
    46  	*clientImpl
    47  
    48  	// This mu protects all the fields, including the embedded clientImpl above.
    49  	mu       sync.Mutex
    50  	refCount int
    51  }
    52  
    53  // New returns a new xdsClient configured by the bootstrap file specified in env
    54  // variable GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG.
    55  //
    56  // The returned xdsClient is a singleton. This function creates the xds client
    57  // if it doesn't already exist.
    58  //
    59  // Note that the first invocation of New() or NewWithConfig() sets the client
    60  // singleton. The following calls will return the singleton xds client without
    61  // checking or using the config.
    62  func New() (XDSClient, error) {
    63  	// This cannot just return newRefCounted(), because in error cases, the
    64  	// returned nil is a typed nil (*clientRefCounted), which may cause nil
    65  	// checks fail.
    66  	c, err := newRefCounted()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return c, nil
    71  }
    72  
    73  func newRefCounted() (*clientRefCounted, error) {
    74  	singletonClient.mu.Lock()
    75  	defer singletonClient.mu.Unlock()
    76  	// If the client implementation was created, increment ref count and return
    77  	// the client.
    78  	if singletonClient.clientImpl != nil {
    79  		singletonClient.refCount++
    80  		return singletonClient, nil
    81  	}
    82  
    83  	// Create the new client implementation.
    84  	config, err := bootstrapNewConfig()
    85  	if err != nil {
    86  		return nil, fmt.Errorf("xds: failed to read bootstrap file: %v", err)
    87  	}
    88  	c, err := newWithConfig(config, defaultWatchExpiryTimeout, defaultIdleAuthorityDeleteTimeout)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	singletonClient.clientImpl = c
    94  	singletonClient.refCount++
    95  	return singletonClient, nil
    96  }
    97  
    98  // NewWithConfig returns a new xdsClient configured by the given config.
    99  //
   100  // The returned xdsClient is a singleton. This function creates the xds client
   101  // if it doesn't already exist.
   102  //
   103  // Note that the first invocation of New() or NewWithConfig() sets the client
   104  // singleton. The following calls will return the singleton xds client without
   105  // checking or using the config.
   106  //
   107  // This function is internal only, for c2p resolver and testing to use. DO NOT
   108  // use this elsewhere. Use New() instead.
   109  func NewWithConfig(config *bootstrap.Config) (XDSClient, error) {
   110  	singletonClient.mu.Lock()
   111  	defer singletonClient.mu.Unlock()
   112  	// If the client implementation was created, increment ref count and return
   113  	// the client.
   114  	if singletonClient.clientImpl != nil {
   115  		singletonClient.refCount++
   116  		return singletonClient, nil
   117  	}
   118  
   119  	// Create the new client implementation.
   120  	c, err := newWithConfig(config, defaultWatchExpiryTimeout, defaultIdleAuthorityDeleteTimeout)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	singletonClient.clientImpl = c
   126  	singletonClient.refCount++
   127  	return singletonClient, nil
   128  }
   129  
   130  // Close closes the client. It does ref count of the xds client implementation,
   131  // and closes the gRPC connection to the management server when ref count
   132  // reaches 0.
   133  func (c *clientRefCounted) Close() {
   134  	c.mu.Lock()
   135  	defer c.mu.Unlock()
   136  	c.refCount--
   137  	if c.refCount == 0 {
   138  		c.clientImpl.Close()
   139  		// Set clientImpl back to nil. So if New() is called after this, a new
   140  		// implementation will be created.
   141  		c.clientImpl = nil
   142  	}
   143  }
   144  
   145  // NewWithConfigForTesting is exported for testing only.
   146  //
   147  // Note that this function doesn't set the singleton, so that the testing states
   148  // don't leak.
   149  func NewWithConfigForTesting(config *bootstrap.Config, watchExpiryTimeout time.Duration) (XDSClient, error) {
   150  	cl, err := newWithConfig(config, watchExpiryTimeout, defaultIdleAuthorityDeleteTimeout)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	return &clientRefCounted{clientImpl: cl, refCount: 1}, nil
   155  }
   156  
   157  // NewClientWithBootstrapContents returns an xds client for this config,
   158  // separate from the global singleton.  This should be used for testing
   159  // purposes only.
   160  func NewClientWithBootstrapContents(contents []byte) (XDSClient, error) {
   161  	// Normalize the contents
   162  	buf := bytes.Buffer{}
   163  	err := json.Indent(&buf, contents, "", "")
   164  	if err != nil {
   165  		return nil, fmt.Errorf("xds: error normalizing JSON: %v", err)
   166  	}
   167  	contents = bytes.TrimSpace(buf.Bytes())
   168  
   169  	clientsMu.Lock()
   170  	defer clientsMu.Unlock()
   171  	if c := clients[string(contents)]; c != nil {
   172  		c.mu.Lock()
   173  		// Since we don't remove the *Client from the map when it is closed, we
   174  		// need to recreate the impl if the ref count dropped to zero.
   175  		if c.refCount > 0 {
   176  			c.refCount++
   177  			c.mu.Unlock()
   178  			return c, nil
   179  		}
   180  		c.mu.Unlock()
   181  	}
   182  
   183  	bcfg, err := bootstrap.NewConfigFromContents(contents)
   184  	if err != nil {
   185  		return nil, fmt.Errorf("xds: error with bootstrap config: %v", err)
   186  	}
   187  
   188  	cImpl, err := newWithConfig(bcfg, defaultWatchExpiryTimeout, defaultIdleAuthorityDeleteTimeout)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	c := &clientRefCounted{clientImpl: cImpl, refCount: 1}
   194  	clients[string(contents)] = c
   195  	return c, nil
   196  }
   197  
   198  var (
   199  	clients   = map[string]*clientRefCounted{}
   200  	clientsMu sync.Mutex
   201  )