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  }