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 )