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 )