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