github.com/cloudwego/kitex@v0.9.0/client/service_inline_test.go (about) 1 /* 2 * Copyright 2023 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package client 18 19 import ( 20 "context" 21 "errors" 22 "runtime" 23 "runtime/debug" 24 "sync/atomic" 25 "testing" 26 "time" 27 28 "github.com/golang/mock/gomock" 29 30 "github.com/cloudwego/kitex/client/callopt" 31 "github.com/cloudwego/kitex/internal/client" 32 "github.com/cloudwego/kitex/internal/mocks" 33 internal_server "github.com/cloudwego/kitex/internal/server" 34 "github.com/cloudwego/kitex/internal/test" 35 "github.com/cloudwego/kitex/pkg/consts" 36 "github.com/cloudwego/kitex/pkg/endpoint" 37 "github.com/cloudwego/kitex/pkg/kerrors" 38 "github.com/cloudwego/kitex/pkg/rpcinfo" 39 "github.com/cloudwego/kitex/pkg/rpcinfo/remoteinfo" 40 "github.com/cloudwego/kitex/pkg/serviceinfo" 41 ) 42 43 type serverInitialInfoImpl struct { 44 EndpointsFunc func(ctx context.Context, req, resp interface{}) (err error) 45 } 46 47 func (s serverInitialInfoImpl) Endpoints() endpoint.Endpoint { 48 if s.EndpointsFunc != nil { 49 return s.EndpointsFunc 50 } 51 return func(ctx context.Context, req, resp interface{}) (err error) { 52 return nil 53 } 54 } 55 56 func (s serverInitialInfoImpl) Option() *internal_server.Options { 57 return internal_server.NewOptions(nil) 58 } 59 60 func (s serverInitialInfoImpl) GetServiceInfos() map[string]*serviceinfo.ServiceInfo { 61 return nil 62 } 63 64 func newMockServerInitialInfo() ServerInitialInfo { 65 return &serverInitialInfoImpl{} 66 } 67 68 func newMockServiceInlineClient(tb testing.TB, ctrl *gomock.Controller, extra ...Option) Client { 69 opts := []Option{ 70 WithTransHandlerFactory(newMockCliTransHandlerFactory(ctrl)), 71 WithResolver(resolver404(ctrl)), 72 WithDialer(newDialer(ctrl)), 73 WithDestService("destService"), 74 } 75 opts = append(opts, extra...) 76 svcInfo := mocks.ServiceInfo() 77 78 cli, err := NewServiceInlineClient(svcInfo, newMockServerInitialInfo(), opts...) 79 test.Assert(tb, err == nil) 80 81 return cli 82 } 83 84 func TestServiceInlineCall(t *testing.T) { 85 ctrl := gomock.NewController(t) 86 defer ctrl.Finish() 87 88 mtd := mocks.MockMethod 89 cli := newMockServiceInlineClient(t, ctrl) 90 ctx := context.Background() 91 req := new(MockTStruct) 92 res := new(MockTStruct) 93 94 err := cli.Call(ctx, mtd, req, res) 95 test.Assert(t, err == nil, err) 96 } 97 98 func TestServiceInlineTagOptions(t *testing.T) { 99 ctrl := gomock.NewController(t) 100 defer ctrl.Finish() 101 102 mtd := mocks.MockMethod 103 tgs := map[string]string{} 104 cls := func(m map[string]string) { 105 for k := range m { 106 delete(m, k) 107 } 108 } 109 md := func(next endpoint.Endpoint) endpoint.Endpoint { 110 return func(ctx context.Context, req, res interface{}) error { 111 ri := rpcinfo.GetRPCInfo(ctx) 112 test.Assert(t, ri != nil) 113 114 to := ri.To() 115 test.Assert(t, to != nil) 116 117 re := remoteinfo.AsRemoteInfo(to) 118 test.Assert(t, re != nil) 119 120 for k, v := range tgs { 121 val, ok := re.Tag(k) 122 test.Assertf(t, ok, "expected tag not found: %s", k) 123 test.Assertf(t, v == val, "values of tag '%s' not equal: '%s' != '%s'", k, v, val) 124 } 125 return nil 126 } 127 } 128 var options []client.Option 129 options = append(options, WithMiddleware(md)) 130 131 ctx := context.Background() 132 req := new(MockTStruct) 133 res := new(MockTStruct) 134 135 cli := newMockServiceInlineClient(t, ctrl, options...) 136 err := cli.Call(ctx, mtd, req, res) 137 test.Assert(t, err == nil) 138 139 cls(tgs) 140 tgs["cluster"] = "client cluster" 141 tgs["idc"] = "client idc" 142 cli = newMockServiceInlineClient(t, ctrl, WithTag("cluster", "client cluster"), WithTag("idc", "client idc")) 143 err = cli.Call(ctx, mtd, req, res) 144 test.Assert(t, err == nil) 145 146 cls(tgs) 147 tgs["cluster"] = "call cluster" 148 tgs["idc"] = "call idc" 149 ctx = NewCtxWithCallOptions(ctx, []callopt.Option{ 150 callopt.WithTag("cluster", "cluster"), 151 callopt.WithTag("idc", "idc"), 152 }) 153 err = cli.Call(ctx, mtd, req, res) 154 test.Assert(t, err == nil) 155 } 156 157 func TestServiceInlineTagOptionLocks0(t *testing.T) { 158 ctrl := gomock.NewController(t) 159 defer ctrl.Finish() 160 161 mtd := mocks.MockMethod 162 md := func(next endpoint.Endpoint) endpoint.Endpoint { 163 return func(ctx context.Context, req, res interface{}) error { 164 re := remoteinfo.AsRemoteInfo(rpcinfo.GetRPCInfo(ctx).To()) 165 166 var err error 167 err = re.SetTag("cluster", "clusterx") 168 test.Assert(t, err == nil) 169 test.Assert(t, re.DefaultTag("cluster", "") == "clusterx") 170 171 err = re.SetTag("idc", "idcx") 172 test.Assert(t, err == nil) 173 test.Assert(t, re.DefaultTag("idc", "") == "idcx") 174 return nil 175 } 176 } 177 var options []client.Option 178 options = append(options, WithMiddleware(md)) 179 180 ctx := context.Background() 181 req := new(MockTStruct) 182 res := new(MockTStruct) 183 184 cli := newMockServiceInlineClient(t, ctrl, options...) 185 err := cli.Call(ctx, mtd, req, res) 186 test.Assert(t, err == nil) 187 } 188 189 func TestServiceInlineTagOptionLocks1(t *testing.T) { 190 ctrl := gomock.NewController(t) 191 defer ctrl.Finish() 192 193 mtd := mocks.MockMethod 194 md := func(next endpoint.Endpoint) endpoint.Endpoint { 195 return func(ctx context.Context, req, res interface{}) error { 196 re := remoteinfo.AsRemoteInfo(rpcinfo.GetRPCInfo(ctx).To()) 197 198 var err error 199 err = re.SetTag("cluster", "whatever") 200 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 201 202 err = re.SetTag("idc", "whatever") 203 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 204 return nil 205 } 206 } 207 var options []client.Option 208 options = append(options, WithMiddleware(md)) 209 options = append(options, WithTag("cluster", "client cluster")) 210 options = append(options, WithTag("idc", "client idc")) 211 212 ctx := context.Background() 213 req := new(MockTStruct) 214 res := new(MockTStruct) 215 216 cli := newMockServiceInlineClient(t, ctrl, options...) 217 err := cli.Call(ctx, mtd, req, res) 218 test.Assert(t, err == nil) 219 } 220 221 func TestServiceInlineTagOptionLocks2(t *testing.T) { 222 ctrl := gomock.NewController(t) 223 defer ctrl.Finish() 224 225 mtd := mocks.MockOnewayMethod 226 md := func(next endpoint.Endpoint) endpoint.Endpoint { 227 return func(ctx context.Context, req, res interface{}) error { 228 re := remoteinfo.AsRemoteInfo(rpcinfo.GetRPCInfo(ctx).To()) 229 230 var err error 231 err = re.SetTag("cluster", "whatever") 232 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 233 234 err = re.SetTag("idc", "whatever") 235 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 236 return nil 237 } 238 } 239 var options []client.Option 240 options = append(options, WithMiddleware(md)) 241 242 ctx := context.Background() 243 req := new(MockTStruct) 244 res := new(MockTStruct) 245 246 cli := newMockServiceInlineClient(t, ctrl, options...) 247 ctx = NewCtxWithCallOptions(ctx, []callopt.Option{ 248 callopt.WithTag("cluster", "cluster"), 249 callopt.WithTag("idc", "idc"), 250 }) 251 err := cli.Call(ctx, mtd, req, res) 252 test.Assert(t, err == nil) 253 } 254 255 func TestServiceInlineTimeoutOptions(t *testing.T) { 256 ctrl := gomock.NewController(t) 257 defer ctrl.Finish() 258 259 mtd := mocks.MockMethod 260 md := func(next endpoint.Endpoint) endpoint.Endpoint { 261 return func(ctx context.Context, req, res interface{}) error { 262 ri := rpcinfo.GetRPCInfo(ctx) 263 test.Assert(t, ri != nil) 264 265 cfgs := ri.Config() 266 test.Assert(t, cfgs != nil) 267 268 mcfg := rpcinfo.AsMutableRPCConfig(cfgs) 269 test.Assert(t, mcfg != nil) 270 271 var err error 272 err = mcfg.SetRPCTimeout(time.Hour) 273 test.Assert(t, err == nil) 274 test.Assert(t, cfgs.RPCTimeout() == time.Hour) 275 276 err = mcfg.SetConnectTimeout(time.Hour * 2) 277 test.Assert(t, err == nil) 278 test.Assert(t, cfgs.ConnectTimeout() == time.Hour*2) 279 return nil 280 } 281 } 282 var options []client.Option 283 options = append(options, WithMiddleware(md)) 284 285 ctx := context.Background() 286 req := new(MockTStruct) 287 res := new(MockTStruct) 288 289 cli := newMockServiceInlineClient(t, ctrl, options...) 290 err := cli.Call(ctx, mtd, req, res) 291 test.Assert(t, err == nil) 292 } 293 294 func TestServiceInlineClientFinalizer(t *testing.T) { 295 ctrl := gomock.NewController(t) 296 defer ctrl.Finish() 297 298 debug.SetGCPercent(-1) 299 defer debug.SetGCPercent(100) 300 301 var ms runtime.MemStats 302 runtime.ReadMemStats(&ms) 303 t.Logf("Before new clients, allocation: %f Mb, Number of allocation: %d\n", mb(ms.HeapAlloc), ms.HeapObjects) 304 305 var ( 306 closeCalledCnt int32 307 succeedCnt = 10000 308 failedCnt = 10000 309 cliCnt = succeedCnt + failedCnt 310 ) 311 clis := make([]Client, cliCnt) 312 // clients that init successfully. 313 for i := 0; i < succeedCnt; i++ { 314 svcInfo := mocks.ServiceInfo() 315 mockClient, err := NewClient(svcInfo, WithDestService("destService"), WithShortConnection(), 316 WithCloseCallbacks(func() error { 317 atomic.AddInt32(&closeCalledCnt, 1) 318 return nil 319 })) 320 test.Assert(t, err == nil, err) 321 clis[i] = mockClient 322 } 323 // clients that init failed, closeCallback should be called 324 for i := succeedCnt; i < cliCnt; i++ { 325 mockClient, err := NewClient(svcInfo, WithDestService(""), WithShortConnection(), 326 WithCloseCallbacks(func() error { 327 atomic.AddInt32(&closeCalledCnt, 1) 328 return nil 329 })) 330 test.Assert(t, err != nil, err) 331 clis[i] = mockClient 332 } 333 334 runtime.ReadMemStats(&ms) 335 t.Logf("After new clients, allocation: %f Mb, Number of allocation: %d\n", mb(ms.HeapAlloc), ms.HeapObjects) 336 337 runtime.GC() 338 runtime.ReadMemStats(&ms) 339 firstGCHeapAlloc, firstGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects 340 t.Logf("After first GC, allocation: %f Mb, Number of allocation: %d\n", firstGCHeapAlloc, firstGCHeapObjects) 341 time.Sleep(200 * time.Millisecond) // ensure the finalizer be executed 342 test.Assert(t, atomic.LoadInt32(&closeCalledCnt) == int32(cliCnt)) // ensure CloseCallback of client has been called 343 344 runtime.GC() 345 runtime.ReadMemStats(&ms) 346 secondGCHeapAlloc, secondGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects 347 t.Logf("After second GC, allocation: %f Mb, Number of allocation: %d\n", secondGCHeapAlloc, secondGCHeapObjects) 348 test.Assert(t, secondGCHeapAlloc < firstGCHeapAlloc/2 && secondGCHeapObjects < firstGCHeapObjects/2) 349 } 350 351 func TestServiceInlineMethodKeyCall(t *testing.T) { 352 ctrl := gomock.NewController(t) 353 defer ctrl.Finish() 354 mtd := mocks.MockMethod 355 opts := []Option{ 356 WithTransHandlerFactory(newMockCliTransHandlerFactory(ctrl)), 357 WithResolver(resolver404(ctrl)), 358 WithDialer(newDialer(ctrl)), 359 WithDestService("destService"), 360 } 361 svcInfo := mocks.ServiceInfo() 362 s := serverInitialInfoImpl{} 363 s.EndpointsFunc = func(ctx context.Context, req, resp interface{}) (err error) { 364 test.Assert(t, ctx.Value(consts.CtxKeyMethod) == mtd) 365 return nil 366 } 367 cli, err := NewServiceInlineClient(svcInfo, s, opts...) 368 test.Assert(t, err == nil) 369 ctx := context.Background() 370 req := new(MockTStruct) 371 res := new(MockTStruct) 372 err = cli.Call(ctx, mtd, req, res) 373 test.Assert(t, err == nil, err) 374 }