From 0c4dc4f38342151ab0bb24643376238b205faca5 Mon Sep 17 00:00:00 2001 From: Marina Sakai <118230951+Marina-Sakai@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:54:54 +0800 Subject: [PATCH 01/49] fix(generic): not write generic method name for binary generic exception to align with method names of services not using binary generic (#1308) --- pkg/generic/binarythrift_codec.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/generic/binarythrift_codec.go b/pkg/generic/binarythrift_codec.go index cb6fb94de5..15df02a0ff 100644 --- a/pkg/generic/binarythrift_codec.go +++ b/pkg/generic/binarythrift_codec.go @@ -24,7 +24,6 @@ import ( "github.com/cloudwego/kitex/pkg/remote" "github.com/cloudwego/kitex/pkg/remote/codec" "github.com/cloudwego/kitex/pkg/remote/codec/perrors" - "github.com/cloudwego/kitex/pkg/rpcinfo" "github.com/cloudwego/kitex/pkg/serviceinfo" ) @@ -42,9 +41,6 @@ func (c *binaryThriftCodec) Marshal(ctx context.Context, msg remote.Message, out return perrors.NewProtocolErrorWithMsg("invalid marshal data in rawThriftBinaryCodec: nil") } if msg.MessageType() == remote.Exception { - if ink, ok := msg.RPCInfo().Invocation().(rpcinfo.InvocationSetter); ok { - ink.SetMethodName(serviceinfo.GenericMethod) - } if err := c.thriftCodec.Marshal(ctx, msg, out); err != nil { return perrors.NewProtocolErrorWithMsg(fmt.Sprintf("rawThriftBinaryCodec Marshal exception failed, err: %s", err.Error())) } From 874403ccfc409deecdc996bce8bce25233937aeb Mon Sep 17 00:00:00 2001 From: "felix.fengmin" Date: Wed, 21 Feb 2024 21:34:17 +0800 Subject: [PATCH 02/49] feat: CBSuite custom GetErrorType func --- pkg/circuitbreak/cbsuite.go | 25 ++++++-- pkg/circuitbreak/cbsuite_option.go | 77 +++++++++++++++++++++++++ pkg/circuitbreak/cbsuite_option_test.go | 76 ++++++++++++++++++++++++ pkg/circuitbreak/cbsuite_test.go | 38 ++++++++++++ pkg/circuitbreak/circuitbreak.go | 4 +- pkg/retry/retryer.go | 23 ++++++-- pkg/retry/retryer_test.go | 46 +++++++++++++-- 7 files changed, 271 insertions(+), 18 deletions(-) create mode 100644 pkg/circuitbreak/cbsuite_option.go create mode 100644 pkg/circuitbreak/cbsuite_option_test.go diff --git a/pkg/circuitbreak/cbsuite.go b/pkg/circuitbreak/cbsuite.go index 3b75cefd0f..9623956a6d 100644 --- a/pkg/circuitbreak/cbsuite.go +++ b/pkg/circuitbreak/cbsuite.go @@ -101,14 +101,27 @@ type CBSuite struct { instanceCBConfig instanceCBConfig events event.Queue + + config CBSuiteConfig } // NewCBSuite to build a new CBSuite. // Notice: Should NewCBSuite for every client in this version, // because event.Queue and event.Bus are not shared with all clients now. -func NewCBSuite(genKey GenServiceCBKeyFunc) *CBSuite { - s := &CBSuite{genServiceCBKey: genKey} - s.instanceCBConfig = instanceCBConfig{CBConfig: defaultCBConfig} +func NewCBSuite(genKey GenServiceCBKeyFunc, options ...CBSuiteOption) *CBSuite { + s := &CBSuite{ + genServiceCBKey: genKey, + instanceCBConfig: instanceCBConfig{ + CBConfig: defaultCBConfig, + }, + config: CBSuiteConfig{ + serviceGetErrorTypeFunc: ErrorTypeOnServiceLevel, + instanceGetErrorTypeFunc: ErrorTypeOnInstanceLevel, + }, + } + for _, option := range options { + option(&s.config) + } return s } @@ -130,7 +143,7 @@ func (s *CBSuite) InstanceCBMW() endpoint.Middleware { return NewCircuitBreakerMW(*s.instanceControl, s.instancePanel) } -// ServicePanel return return cb Panel of service +// ServicePanel return cb Panel of service func (s *CBSuite) ServicePanel() circuitbreaker.Panel { if s.servicePanel == nil { s.initServiceCB() @@ -213,7 +226,7 @@ func (s *CBSuite) initServiceCB() { } s.serviceControl = &Control{ GetKey: svcKey, - GetErrorType: ErrorTypeOnServiceLevel, + GetErrorType: s.config.serviceGetErrorTypeFunc, DecorateError: func(ctx context.Context, request interface{}, err error) error { return kerrors.ErrServiceCircuitBreak }, @@ -239,7 +252,7 @@ func (s *CBSuite) initInstanceCB() { } s.instanceControl = &Control{ GetKey: instanceKey, - GetErrorType: ErrorTypeOnInstanceLevel, + GetErrorType: s.config.instanceGetErrorTypeFunc, DecorateError: func(ctx context.Context, request interface{}, err error) error { return kerrors.ErrInstanceCircuitBreak }, diff --git a/pkg/circuitbreak/cbsuite_option.go b/pkg/circuitbreak/cbsuite_option.go new file mode 100644 index 0000000000..64482d2ba7 --- /dev/null +++ b/pkg/circuitbreak/cbsuite_option.go @@ -0,0 +1,77 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package circuitbreak + +import "context" + +type CBSuiteConfig struct { + serviceGetErrorTypeFunc GetErrorTypeFunc + instanceGetErrorTypeFunc GetErrorTypeFunc +} + +type CBSuiteOption func(s *CBSuiteConfig) + +// WithServiceGetErrorType sets serviceControl.GetErrorType +// Kitex will call customFunc to determine the error type for circuit breaker +// Users are recommended to use WithWrappedServiceGetErrorType to keep most of the behaviors +// NOTE: this is used for SERVICE LEVEL circuit breaker +func WithServiceGetErrorType(customFunc GetErrorTypeFunc) CBSuiteOption { + return func(cfg *CBSuiteConfig) { + cfg.serviceGetErrorTypeFunc = customFunc + } +} + +// WithWrappedServiceGetErrorType sets serviceControl.GetErrorType +// Kitex will call ErrorTypeOnServiceLevel first, and if TypeSuccess is returned, customFunc will +// then be called to determine the final error type for circuit breaker +// NOTE: this is used for SERVICE LEVEL circuit breaker +func WithWrappedServiceGetErrorType(customFunc GetErrorTypeFunc) CBSuiteOption { + return func(cfg *CBSuiteConfig) { + cfg.serviceGetErrorTypeFunc = WrapErrorTypeFunc(customFunc, ErrorTypeOnServiceLevel) + } +} + +// WithInstanceGetErrorType sets instanceControl.GetErrorType +// Kitex will call customFunc to determine the error type for circuit breaker +// Users are recommended to use WithWrappedInstanceGetErrorType to keep most of the behaviors +// NOTE: this is used for INSTANCE LEVEL circuit breaker +func WithInstanceGetErrorType(f GetErrorTypeFunc) CBSuiteOption { + return func(cfg *CBSuiteConfig) { + cfg.instanceGetErrorTypeFunc = f + } +} + +// WithWrappedInstanceGetErrorType sets instanceControl.GetErrorType +// Kitex will call ErrorTypeOnInstanceLevel first, and if TypeSuccess is returned, customFunc will +// then be called to determine the final error type for circuit breaker +// NOTE: this is used for INSTANCE LEVEL circuit breaker +func WithWrappedInstanceGetErrorType(f GetErrorTypeFunc) CBSuiteOption { + return func(cfg *CBSuiteConfig) { + cfg.instanceGetErrorTypeFunc = WrapErrorTypeFunc(f, ErrorTypeOnInstanceLevel) + } +} + +// WrapErrorTypeFunc calls the customFunc if the originalFunc returns TypeSuccess +// customFunc may selectively return another type based on business requirement +func WrapErrorTypeFunc(customFunc, originalFunc GetErrorTypeFunc) GetErrorTypeFunc { + return func(ctx context.Context, request, response interface{}, err error) ErrorType { + if errorType := originalFunc(ctx, request, response, err); errorType != TypeSuccess { + return errorType + } + return customFunc(ctx, request, response, err) + } +} diff --git a/pkg/circuitbreak/cbsuite_option_test.go b/pkg/circuitbreak/cbsuite_option_test.go new file mode 100644 index 0000000000..2c22c140be --- /dev/null +++ b/pkg/circuitbreak/cbsuite_option_test.go @@ -0,0 +1,76 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package circuitbreak + +import ( + "context" + "errors" + "testing" + + "github.com/cloudwego/kitex/internal/test" +) + +func TestWrapErrorTypeFunc(t *testing.T) { + originalFunc := func(ctx context.Context, request, response interface{}, err error) ErrorType { + if err != nil { + return TypeTimeout + } + return TypeSuccess + } + + customFunc := func(ctx context.Context, request, response interface{}, err error) ErrorType { + return TypeFailure + } + + f := WrapErrorTypeFunc(customFunc, originalFunc) + + t.Run("no-error", func(t *testing.T) { + tp := f(context.Background(), nil, nil, nil) + test.Assert(t, tp == TypeFailure) // returned by customFunc + }) + + t.Run("error", func(t *testing.T) { + tp := f(context.Background(), nil, nil, errors.New("error")) + test.Assert(t, tp == TypeTimeout) // returned by originalFunc + }) +} + +func TestWithWrappedServiceGetErrorType(t *testing.T) { + cfg := &CBSuiteConfig{} + + o := WithWrappedServiceGetErrorType(func(ctx context.Context, request, response interface{}, err error) ErrorType { + return TypeIgnorable + }) + o(cfg) + + errType := cfg.serviceGetErrorTypeFunc(context.Background(), nil, nil, nil) + + test.Assert(t, errType == TypeIgnorable) +} + +func TestWithWrappedInstanceGetErrorType(t *testing.T) { + cfg := &CBSuiteConfig{} + + o := WithWrappedInstanceGetErrorType(func(ctx context.Context, request, response interface{}, err error) ErrorType { + return TypeIgnorable + }) + o(cfg) + + errType := cfg.instanceGetErrorTypeFunc(context.Background(), nil, nil, nil) + + test.Assert(t, errType == TypeIgnorable) +} diff --git a/pkg/circuitbreak/cbsuite_test.go b/pkg/circuitbreak/cbsuite_test.go index 09a05d4920..24b9ebabb2 100644 --- a/pkg/circuitbreak/cbsuite_test.go +++ b/pkg/circuitbreak/cbsuite_test.go @@ -40,10 +40,18 @@ var ( addrMock = utils.NewNetAddr("mock", "127.0.0.1:8888") ) +func sameFuncPointer(f1, f2 interface{}) bool { + return reflect.ValueOf(f1).Pointer() == reflect.ValueOf(f2).Pointer() +} + func TestNewCBSuite(t *testing.T) { cb := NewCBSuite(RPCInfo2Key) test.Assert(t, cb.servicePanel == nil) test.Assert(t, cb.instancePanel == nil) + test.Assert(t, cb.instanceCBConfig.CBConfig == defaultCBConfig) + test.Assert(t, sameFuncPointer(cb.genServiceCBKey, RPCInfo2Key)) + test.Assert(t, sameFuncPointer(cb.config.instanceGetErrorTypeFunc, ErrorTypeOnInstanceLevel)) + test.Assert(t, sameFuncPointer(cb.config.serviceGetErrorTypeFunc, ErrorTypeOnServiceLevel)) var mws []endpoint.Middleware mws = append(mws, cb.ServiceCBMW()) @@ -77,6 +85,36 @@ func TestGetServiceCB(t *testing.T) { test.Assert(t, cb.serviceControl != nil) } +func TestNewCBSuiteOptions(t *testing.T) { + t.Run("WithServiceGetErrorType", func(t *testing.T) { + // prepare + f := func(ctx context.Context, request, response interface{}, err error) ErrorType { + return TypeSuccess + } + + // test + cb := NewCBSuite(RPCInfo2Key, WithServiceGetErrorType(f)) + + // check + ctl := cb.ServiceControl() + test.Assert(t, sameFuncPointer(ctl.GetErrorType, f)) + }) + t.Run("WithInstanceControl", func(t *testing.T) { + // prepare + f := func(ctx context.Context, request, response interface{}, err error) ErrorType { + return TypeSuccess + } + + // test + cb := NewCBSuite(RPCInfo2Key, WithInstanceGetErrorType(f)) + cb.initInstanceCB() + + // check + ctl := cb.instanceControl + test.Assert(t, sameFuncPointer(ctl.GetErrorType, f)) + }) +} + func TestServiceCB(t *testing.T) { cb := NewCBSuite(RPCInfo2Key) cb.initServiceCB() diff --git a/pkg/circuitbreak/circuitbreak.go b/pkg/circuitbreak/circuitbreak.go index fee053cb05..f7e3c53668 100644 --- a/pkg/circuitbreak/circuitbreak.go +++ b/pkg/circuitbreak/circuitbreak.go @@ -59,13 +59,15 @@ func WrapErrorWithType(err error, errorType ErrorType) CircuitBreakerAwareError return &errorWrapperWithType{err: err, errType: errorType} } +type GetErrorTypeFunc func(ctx context.Context, request, response interface{}, err error) ErrorType + // Control is the control strategy of the circuit breaker. type Control struct { // Implement this to generate a key for the circuit breaker panel. GetKey func(ctx context.Context, request interface{}) (key string, enabled bool) // Implement this to determine the type of error. - GetErrorType func(ctx context.Context, request, response interface{}, err error) ErrorType + GetErrorType GetErrorTypeFunc // Implement this to provide more detailed information about the circuit breaker. // The err argument is always a kerrors.ErrCircuitBreak. diff --git a/pkg/retry/retryer.go b/pkg/retry/retryer.go index 9637a0ecb2..828301adec 100644 --- a/pkg/retry/retryer.go +++ b/pkg/retry/retryer.go @@ -69,8 +69,8 @@ func NewRetryContainerWithCB(cc *circuitbreak.Control, cp circuitbreaker.Panel) return NewRetryContainer(WithContainerCBControl(cc), WithContainerCBPanel(cp)) } -func newCBSuite() *circuitbreak.CBSuite { - return circuitbreak.NewCBSuite(circuitbreak.RPCInfo2Key) +func newCBSuite(opts []circuitbreak.CBSuiteOption) *circuitbreak.CBSuite { + return circuitbreak.NewCBSuite(circuitbreak.RPCInfo2Key, opts...) } // NewRetryContainerWithCBStat build Container that need to do circuit breaker statistic. @@ -101,7 +101,14 @@ func WithContainerCBSuite(cbs *circuitbreak.CBSuite) ContainerOption { } } -// WithContainerCBControl is specifies the circuitbreak.Control used in the retry circuitbreaker +// WithContainerCBSuiteOptions specifies the circuitbreak.CBSuiteOption for initializing circuitbreak.CBSuite +func WithContainerCBSuiteOptions(opts ...circuitbreak.CBSuiteOption) ContainerOption { + return func(rc *Container) { + rc.cbContainer.cbSuiteOptions = opts + } +} + +// WithContainerCBControl specifies the circuitbreak.Control used in the retry circuitbreaker // It's user's responsibility to make sure it's paired with panel func WithContainerCBControl(ctrl *circuitbreak.Control) ContainerOption { return func(rc *Container) { @@ -109,7 +116,7 @@ func WithContainerCBControl(ctrl *circuitbreak.Control) ContainerOption { } } -// WithContainerCBPanel is specifies the circuitbreaker.Panel used in the retry circuitbreaker +// WithContainerCBPanel specifies the circuitbreaker.Panel used in the retry circuitbreaker // It's user's responsibility to make sure it's paired with control func WithContainerCBPanel(panel circuitbreaker.Panel) ContainerOption { return func(rc *Container) { @@ -148,14 +155,15 @@ func NewRetryContainer(opts ...ContainerOption) *Container { // ignore cbSuite/cbCtl/cbPanel options rc.cbContainer = &cbContainer{ enablePercentageLimit: true, - cbSuite: newCBSuite(), + cbSuite: newCBSuite(rc.cbContainer.cbSuiteOptions), + cbSuiteOptions: rc.cbContainer.cbSuiteOptions, } } container := rc.cbContainer if container.cbCtl == nil && container.cbPanel == nil { if container.cbSuite == nil { - container.cbSuite = newCBSuite() + container.cbSuite = newCBSuite(rc.cbContainer.cbSuiteOptions) container.cbStat = true } container.cbCtl = container.cbSuite.ServiceControl() @@ -199,6 +207,9 @@ type cbContainer struct { // If enabled, Kitex will always create a cbSuite and use its cbCtl & cbPanel, and retryer will call // recordRetryStat before rpcCall, to precisely control the percentage of retry requests over all requests. enablePercentageLimit bool + + // for creating CBSuite inside NewRetryContainer + cbSuiteOptions []circuitbreak.CBSuiteOption } // IsValid returns true when both cbCtl & cbPanel are not nil diff --git a/pkg/retry/retryer_test.go b/pkg/retry/retryer_test.go index b5cf75197d..20110f6191 100644 --- a/pkg/retry/retryer_test.go +++ b/pkg/retry/retryer_test.go @@ -18,12 +18,14 @@ package retry import ( "context" + "reflect" "sync" "sync/atomic" "testing" "time" "github.com/cloudwego/kitex/internal/test" + "github.com/cloudwego/kitex/pkg/circuitbreak" "github.com/cloudwego/kitex/pkg/discovery" "github.com/cloudwego/kitex/pkg/kerrors" "github.com/cloudwego/kitex/pkg/remote" @@ -915,7 +917,7 @@ func TestNewRetryContainerWithOptions(t *testing.T) { }) t.Run("percentage_limit&&cbOptions", func(t *testing.T) { - cbSuite := newCBSuite() + cbSuite := newCBSuite(nil) rc := NewRetryContainer( WithContainerEnablePercentageLimit(), WithContainerCBSuite(cbSuite), @@ -934,23 +936,57 @@ func TestNewRetryContainerWithOptions(t *testing.T) { }) t.Run("cb_suite", func(t *testing.T) { - cbs := newCBSuite() + cbs := newCBSuite(nil) rc := NewRetryContainer(WithContainerCBSuite(cbs)) test.Assert(t, rc.cbContainer.cbSuite == cbs, "cbSuite expected %p, got %p", cbs, rc.cbContainer.cbSuite) }) t.Run("cb_control&cb_panel", func(t *testing.T) { - cbs := newCBSuite() + cbs := newCBSuite(nil) rc := NewRetryContainer( WithContainerCBControl(cbs.ServiceControl()), WithContainerCBPanel(cbs.ServicePanel())) test.Assert(t, rc.cbContainer.cbCtl == cbs.ServiceControl(), "cbControl not match") test.Assert(t, rc.cbContainer.cbPanel == cbs.ServicePanel(), "cbPanel not match") }) + + t.Run("CBSuiteOptions", func(t *testing.T) { + f := func(ctx context.Context, request, response interface{}, err error) circuitbreak.ErrorType { + return circuitbreak.TypeSuccess + } + opts := []circuitbreak.CBSuiteOption{ + circuitbreak.WithServiceGetErrorType(f), + } + + // test + rc := NewRetryContainer(WithContainerCBSuiteOptions(opts...)) + serviceControl := rc.cbContainer.cbSuite.ServiceControl() + + // check + test.Assert(t, len(rc.cbContainer.cbSuiteOptions) == 1) + test.Assert(t, reflect.ValueOf(serviceControl.GetErrorType).Pointer() == reflect.ValueOf(f).Pointer()) + }) + + t.Run("CBSuiteOptions&PercentageLimit", func(t *testing.T) { + f := func(ctx context.Context, request, response interface{}, err error) circuitbreak.ErrorType { + return circuitbreak.TypeSuccess + } + opts := []circuitbreak.CBSuiteOption{ + circuitbreak.WithServiceGetErrorType(f), + } + + // test + rc := NewRetryContainer(WithContainerEnablePercentageLimit(), WithContainerCBSuiteOptions(opts...)) + serviceControl := rc.cbContainer.cbSuite.ServiceControl() + + // check + test.Assert(t, len(rc.cbContainer.cbSuiteOptions) == 1) + test.Assert(t, reflect.ValueOf(serviceControl.GetErrorType).Pointer() == reflect.ValueOf(f).Pointer()) + }) } func TestNewRetryContainerWithCBStat(t *testing.T) { - cbs := newCBSuite() + cbs := newCBSuite(nil) rc := NewRetryContainerWithCBStat(cbs.ServiceControl(), cbs.ServicePanel()) test.Assert(t, rc.cbContainer.cbCtl == cbs.ServiceControl(), "cbControl not match") test.Assert(t, rc.cbContainer.cbPanel == cbs.ServicePanel(), "cbPanel not match") @@ -959,7 +995,7 @@ func TestNewRetryContainerWithCBStat(t *testing.T) { } func TestNewRetryContainerWithCB(t *testing.T) { - cbs := newCBSuite() + cbs := newCBSuite(nil) rc := NewRetryContainerWithCB(cbs.ServiceControl(), cbs.ServicePanel()) test.Assert(t, rc.cbContainer.cbCtl == cbs.ServiceControl(), "cbControl not match") test.Assert(t, rc.cbContainer.cbPanel == cbs.ServicePanel(), "cbPanel not match") From bce14431d2e6c8946feef2ce14d5681d8b1dd440 Mon Sep 17 00:00:00 2001 From: Marina Sakai <118230951+Marina-Sakai@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:52:34 +0800 Subject: [PATCH 03/49] fix(netpollmux): fix a bug that disables multi-service by assigning the first svcInfo to targetSvcInfo (#1294) --- pkg/remote/trans/netpollmux/server_handler.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/remote/trans/netpollmux/server_handler.go b/pkg/remote/trans/netpollmux/server_handler.go index 028954fc74..eb3dbc3d06 100644 --- a/pkg/remote/trans/netpollmux/server_handler.go +++ b/pkg/remote/trans/netpollmux/server_handler.go @@ -252,7 +252,6 @@ func (t *svrTransHandler) task(muxSvrConnCtx context.Context, conn net.Conn, rea } svcInfo := recvMsg.ServiceInfo() - t.targetSvcInfo = svcInfo if recvMsg.MessageType() == remote.Heartbeat { sendMsg = remote.NewMessage(nil, svcInfo, rpcInfo, remote.Heartbeat, remote.Server) } else { From abf11bf33df95bc2d7ae3b9fd8da9bcccdd56928 Mon Sep 17 00:00:00 2001 From: Joway Date: Fri, 22 Mar 2024 11:15:38 +0800 Subject: [PATCH 04/49] fix(connpool): kitex long pool reset idleList element to nil to prevent conn leak (#1307) --- pkg/remote/connpool/long_pool.go | 41 ++++++++++++++++----------- pkg/remote/connpool/long_pool_test.go | 16 ++++++++++- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/pkg/remote/connpool/long_pool.go b/pkg/remote/connpool/long_pool.go index d5df9d5984..50c0a78c53 100644 --- a/pkg/remote/connpool/long_pool.go +++ b/pkg/remote/connpool/long_pool.go @@ -117,7 +117,7 @@ func newPool(minIdle, maxIdle int, maxIdleTimeout time.Duration) *pool { // pool implements a pool of long connections. type pool struct { - idleList []*longConn + idleList []*longConn // idleList Get/Put by FILO(stack) but Evict by FIFO(queue) mu sync.RWMutex // config minIdle int @@ -130,24 +130,26 @@ func (p *pool) Get() (*longConn, bool, int) { p.mu.Lock() // Get the first active one n := len(p.idleList) - i := n - 1 - for ; i >= 0; i-- { - o := p.idleList[i] + selected := n - 1 + for ; selected >= 0; selected-- { + o := p.idleList[selected] + // reset slice element to nil, active conn object only could be hold reference by user function + p.idleList[selected] = nil if o.IsActive() { - p.idleList = p.idleList[:i] + p.idleList = p.idleList[:selected] p.mu.Unlock() - return o, true, n - i + return o, true, n - selected } // inactive object o.Close() } // in case all objects are inactive - if i < 0 { - i = 0 + if selected < 0 { + selected = 0 } - p.idleList = p.idleList[:i] + p.idleList = p.idleList[:selected] p.mu.Unlock() - return nil, false, n - i + return nil, false, n - selected } // Put puts back a connection to the pool. @@ -164,19 +166,24 @@ func (p *pool) Put(o *longConn) bool { } // Evict cleanups the expired connections. -func (p *pool) Evict() int { +// Evict returns how many connections has been evicted. +func (p *pool) Evict() (evicted int) { p.mu.Lock() - i := 0 - for ; i < len(p.idleList)-p.minIdle; i++ { - if !p.idleList[i].Expired() { + nonIdle := len(p.idleList) - p.minIdle + // clear non idle connections + for ; evicted < nonIdle; evicted++ { + if !p.idleList[evicted].Expired() { break } // close the inactive object - p.idleList[i].Close() + p.idleList[evicted].Close() + // reset slice element to nil, otherwise it will cause the connections will never be destroyed + // TODO: use clear(unused_slice) when we no need to care about < go1.18 + p.idleList[evicted] = nil } - p.idleList = p.idleList[i:] + p.idleList = p.idleList[evicted:] p.mu.Unlock() - return i + return evicted } // Len returns the length of the pool. diff --git a/pkg/remote/connpool/long_pool_test.go b/pkg/remote/connpool/long_pool_test.go index 90c5052aa1..e086ab7365 100644 --- a/pkg/remote/connpool/long_pool_test.go +++ b/pkg/remote/connpool/long_pool_test.go @@ -22,7 +22,9 @@ import ( "fmt" "math/rand" "net" + "runtime" "sync" + "sync/atomic" "testing" "time" @@ -831,8 +833,12 @@ func TestLongConnPoolEvict(t *testing.T) { opt := dialer.ConnOption{Dialer: d, ConnectTimeout: time.Second} // get a new conn var conns []net.Conn + aliveconns := int32(maxIdle) for i := 0; i < maxIdle; i++ { c, err := p.Get(context.TODO(), network, address, opt) + runtime.SetFinalizer(c, func(_ interface{}) { + atomic.AddInt32(&aliveconns, -1) + }) test.Assert(t, err == nil) conns = append(conns, c) } @@ -841,6 +847,7 @@ func TestLongConnPoolEvict(t *testing.T) { err := p.Put(conns[i]) test.Assert(t, err == nil) } + conns = []net.Conn{} // only `minIdle` of connections should be kept in the pool // 3 times of idleTime to make sure the eviction goroutine can be done @@ -850,10 +857,17 @@ func TestLongConnPoolEvict(t *testing.T) { test.Assert(t, v.Len() == minIdle) return true }) + for i := 0; i < 3; i++ { + runtime.GC() + if atomic.LoadInt32(&aliveconns) == int32(minIdle) { + break + } + time.Sleep(time.Second) + } + test.DeepEqual(t, atomic.LoadInt32(&aliveconns), int32(minIdle)) // globalIdle should also be decreased when evicting test.Assert(t, int(p.globalIdle.Now()) == minIdle, p.globalIdle.Now()) // get after eviction - conns = []net.Conn{} for i := 0; i < maxIdle; i++ { c, err := p.Get(context.TODO(), network, address, opt) test.Assert(t, err == nil) From 13beb23c17f602663262ed30fb4dc2967f9014d2 Mon Sep 17 00:00:00 2001 From: "felix.fengmin" Date: Mon, 8 Apr 2024 14:21:05 +0800 Subject: [PATCH 05/49] chore: frugal v0.1.15 (with migrated iasm) --- go.mod | 2 +- go.sum | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 8b46f8b40b..37ccd6878c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/cloudwego/configmanager v0.2.0 github.com/cloudwego/dynamicgo v0.2.0 github.com/cloudwego/fastpb v0.0.4 - github.com/cloudwego/frugal v0.1.14 + github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 github.com/cloudwego/netpoll v0.6.0 github.com/cloudwego/thriftgo v0.3.6 diff --git a/go.sum b/go.sum index 758ca39b9f..31e189be23 100644 --- a/go.sum +++ b/go.sum @@ -35,9 +35,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.0.0-20220818063314-28c361dae733/go.mod h1:wOQ0nsbeOLa2awv8bUYFW/EHXbjQMlZ10fAlXDB2sz8= github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/choleraehyq/pid v0.0.13/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= github.com/choleraehyq/pid v0.0.15/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= github.com/choleraehyq/pid v0.0.16/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= @@ -57,8 +56,10 @@ github.com/cloudwego/fastpb v0.0.4 h1:/ROVVfoFtpfc+1pkQLzGs+azjxUbSOsAqSY4tAAx4m github.com/cloudwego/fastpb v0.0.4/go.mod h1:/V13XFTq2TUkxj2qWReV8MwfPC4NnPcy6FsrojnsSG0= github.com/cloudwego/frugal v0.1.3/go.mod h1:b981ViPYdhI56aFYsoMjl9kv6yeqYSO+iEz2jrhkCgI= github.com/cloudwego/frugal v0.1.6/go.mod h1:9ElktKsh5qd2zDBQ5ENhPSQV7F2dZ/mXlr1eaZGDBFs= -github.com/cloudwego/frugal v0.1.14 h1:vkjQMb5OsPL779RfMdLI4YJZsOH8fR0ewJpTuAVSeiQ= -github.com/cloudwego/frugal v0.1.14/go.mod h1:zFBA63ne4+Tz4qayRZFZf+ZVwGqTzb+1Xe3ZDCq+Wfc= +github.com/cloudwego/frugal v0.1.15 h1:LC55UJKhQPMFVjDPbE+LJcF7etZjSx6uokG1tk0wPK0= +github.com/cloudwego/frugal v0.1.15/go.mod h1:26kU1r18vA8vRg12c66XPDlfv1GQHDbE1RpusipXfcI= +github.com/cloudwego/iasm v0.0.9 h1:DgNtfPjuz3YAQ0hmmiGg6DkDGj+foARFSwu7vKFPT1o= +github.com/cloudwego/iasm v0.0.9/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/kitex v0.3.2/go.mod h1:/XD07VpUD9VQWmmoepASgZ6iw//vgWikVA9MpzLC5i0= github.com/cloudwego/kitex v0.4.4/go.mod h1:3FcH5h9Qw+dhRljSzuGSpWuThttA8DvK0BsL7HUYydo= github.com/cloudwego/kitex v0.6.1/go.mod h1:zI1GBrjT0qloTikcCfQTgxg3Ws+yQMyaChEEOcGNUvA= From 3e665fd737ee226350b9b354e6fa136aca6de9bc Mon Sep 17 00:00:00 2001 From: Joway Date: Thu, 11 Apr 2024 17:17:56 +0800 Subject: [PATCH 06/49] perf(codec): fast codec use batch alloc (#1320) --- .../pluginmode/thriftgo/struct_tpl.go | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go index 734ce0bf78..71cf89dd1c 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go +++ b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go @@ -174,7 +174,11 @@ const structLikeFastReadField = ` func (p *{{$TypeName}}) FastReadField{{Str .ID}}(buf []byte) (int, error) { offset := 0 {{- $ctx := MkRWCtx .}} + {{- $target := print $ctx.Target }} + {{- $ctx = $ctx.WithDecl.WithTarget "_field"}} {{ template "FieldFastRead" $ctx}} + {{/* line break */}} + {{- $target}} = _field return offset, nil } {{- end}}{{/* range .Fields */}} @@ -343,15 +347,12 @@ const fieldFastReadStructLike = ` {{define "FieldFastReadStructLike"}} {{- if .NeedDecl}} {{- .Target}} := {{.TypeName.Deref.NewFunc}}() - {{- else}} - tmp := {{.TypeName.Deref.NewFunc}}() {{- end}} - if l, err := {{- if .NeedDecl}}{{.Target}}{{else}}tmp{{end}}.FastRead(buf[offset:]); err != nil { + if l, err := {{- .Target}}.FastRead(buf[offset:]); err != nil { return offset, err } else { offset += l } - {{if not .NeedDecl}}{{- .Target}} = tmp{{end}} {{- end}}{{/* define "FieldFastReadStructLike" */}} ` @@ -397,19 +398,28 @@ const fieldFastReadContainer = ` const fieldFastReadMap = ` {{define "FieldFastReadMap"}} +{{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} _, _, size, l, err := bthrift.Binary.ReadMapBegin(buf[offset:]) offset += l if err != nil { return offset, err } {{.Target}} {{if .NeedDecl}}:{{end}}= make({{.TypeName}}, size) + {{- if $isStructVal}} + values := make([]{{.ValCtx.TypeName.Deref}}, size) + {{- end}} for i := 0; i < size; i++ { {{- $key := .GenID "_key"}} {{- $ctx := .KeyCtx.WithDecl.WithTarget $key}} {{- template "FieldFastRead" $ctx}} {{/* line break */}} {{- $val := .GenID "_val"}} - {{- $ctx := .ValCtx.WithDecl.WithTarget $val}} + {{- $ctx := .ValCtx.WithTarget $val}} + {{- if $isStructVal}} + {{$val}} := &values[i] + {{- else}} + {{- $ctx = $ctx.WithDecl}} + {{- end}} {{- template "FieldFastRead" $ctx}} {{if and .ValCtx.Type.Category.IsStructLike Features.ValueTypeForSIC}} @@ -428,21 +438,28 @@ const fieldFastReadMap = ` const fieldFastReadSet = ` {{define "FieldFastReadSet"}} +{{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} _, size, l, err := bthrift.Binary.ReadSetBegin(buf[offset:]) offset += l if err != nil { return offset, err } {{.Target}} {{if .NeedDecl}}:{{end}}= make({{.TypeName}}, 0, size) + {{- if $isStructVal}} + values := make([]{{.ValCtx.TypeName.Deref}}, size) + {{- end}} for i := 0; i < size; i++ { {{- $val := .GenID "_elem"}} - {{- $ctx := .ValCtx.WithDecl.WithTarget $val}} + {{- $ctx := .ValCtx.WithTarget $val}} + {{- if $isStructVal}} + {{$val}} := &values[i] + {{- else}} + {{- $ctx = $ctx.WithDecl}} + {{- end}} {{- template "FieldFastRead" $ctx}} - {{if and .ValCtx.Type.Category.IsStructLike Features.ValueTypeForSIC}} {{$val = printf "*%s" $val}} {{end}} - {{.Target}} = append({{.Target}}, {{$val}}) } if l, err := bthrift.Binary.ReadSetEnd(buf[offset:]); err != nil { @@ -455,21 +472,28 @@ const fieldFastReadSet = ` const fieldFastReadList = ` {{define "FieldFastReadList"}} +{{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} _, size, l, err := bthrift.Binary.ReadListBegin(buf[offset:]) offset += l if err != nil { return offset, err } {{.Target}} {{if .NeedDecl}}:{{end}}= make({{.TypeName}}, 0, size) + {{- if $isStructVal}} + values := make([]{{.ValCtx.TypeName.Deref}}, size) + {{- end}} for i := 0; i < size; i++ { {{- $val := .GenID "_elem"}} - {{- $ctx := .ValCtx.WithDecl.WithTarget $val}} + {{- $ctx := .ValCtx.WithTarget $val}} + {{- if $isStructVal}} + {{$val}} := &values[i] + {{- else}} + {{- $ctx = $ctx.WithDecl}} + {{- end}} {{- template "FieldFastRead" $ctx}} - {{if and .ValCtx.Type.Category.IsStructLike Features.ValueTypeForSIC}} {{$val = printf "*%s" $val}} {{end}} - {{.Target}} = append({{.Target}}, {{$val}}) } if l, err := bthrift.Binary.ReadListEnd(buf[offset:]); err != nil { @@ -557,11 +581,9 @@ const fieldDeepCopyList = ` for _, {{SourceTarget $val}} := range {{$Src}} { {{- $ctx := .ValCtx.WithDecl.WithTarget $val}} {{- template "FieldDeepCopy" $ctx}} - {{- if and .ValCtx.Type.Category.IsStructLike Features.ValueTypeForSIC}} {{$val = printf "*%s" $val}} {{- end}} - {{.Target}} = append({{.Target}}, {{$val}}) } } @@ -578,11 +600,9 @@ const fieldDeepCopySet = ` for _, {{SourceTarget $val}} := range {{$Src}} { {{- $ctx := .ValCtx.WithDecl.WithTarget $val}} {{- template "FieldDeepCopy" $ctx}} - {{- if and .ValCtx.Type.Category.IsStructLike Features.ValueTypeForSIC}} {{$val = printf "*%s" $val}} {{- end}} - {{.Target}} = append({{.Target}}, {{$val}}) } } From 8b3dd951e7c1bd6bfab7420cf2d5410966503986 Mon Sep 17 00:00:00 2001 From: "yuxuan.wang1" Date: Sun, 31 Mar 2024 23:10:24 +0800 Subject: [PATCH 07/49] feat: implement skipDecoder to enable Frugal and FastCodec for standard Thrift Binary Protocol --- pkg/remote/codec/thrift/skip_decoder.go | 201 ++++++++++++++++++ pkg/remote/codec/thrift/skip_decoder_test.go | 111 ++++++++++ pkg/remote/codec/thrift/thrift_data.go | 84 ++++++-- pkg/remote/codec/thrift/thrift_data_test.go | 49 +++++ pkg/remote/codec/thrift/thrift_frugal.go | 6 +- pkg/remote/codec/thrift/thrift_frugal_test.go | 44 +++- pkg/remote/codec/thrift/thrift_others.go | 2 +- 7 files changed, 476 insertions(+), 21 deletions(-) create mode 100644 pkg/remote/codec/thrift/skip_decoder.go create mode 100644 pkg/remote/codec/thrift/skip_decoder_test.go diff --git a/pkg/remote/codec/thrift/skip_decoder.go b/pkg/remote/codec/thrift/skip_decoder.go new file mode 100644 index 0000000000..3da60b042e --- /dev/null +++ b/pkg/remote/codec/thrift/skip_decoder.go @@ -0,0 +1,201 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package thrift + +import ( + "errors" + "fmt" + + "github.com/apache/thrift/lib/go/thrift" + + "github.com/cloudwego/kitex/pkg/remote" + "github.com/cloudwego/kitex/pkg/remote/codec/perrors" +) + +const ( + EnableSkipDecoder CodecType = 0b10000 +) + +// skipBuffer wraps remote.ByteBuffer and reimplement the Next method. +// Next method would not advance the reader in the underlying buffer until Buffer method is called. +// It is used to fit in BinaryProtocol and reduce memory allocation. +type skipBuffer struct { + remote.ByteBuffer + readNum int +} + +func (b *skipBuffer) Next(n int) ([]byte, error) { + prev := b.readNum + next := prev + n + buf, err := b.ByteBuffer.Peek(next) + if err != nil { + return nil, err + } + b.readNum = next + return buf[prev:next], nil +} + +func (b *skipBuffer) Buffer() ([]byte, error) { + return b.ByteBuffer.Next(b.readNum) +} + +func newSkipBuffer(bb remote.ByteBuffer) *skipBuffer { + return &skipBuffer{ + ByteBuffer: bb, + } +} + +// skipDecoder is used to parse the input byte-by-byte and skip the thrift payload +// for making use of Frugal and FastCodec in standard Thrift Binary Protocol scenario. +type skipDecoder struct { + tprot *BinaryProtocol + sb *skipBuffer +} + +func newSkipDecoder(trans remote.ByteBuffer) *skipDecoder { + sb := newSkipBuffer(trans) + return &skipDecoder{ + tprot: NewBinaryProtocol(sb), + sb: sb, + } +} + +func (sd *skipDecoder) SkipStruct() error { + return sd.skip(thrift.STRUCT, thrift.DEFAULT_RECURSION_DEPTH) +} + +func (sd *skipDecoder) skipString() error { + size, err := sd.tprot.ReadI32() + if err != nil { + return err + } + if size < 0 { + return perrors.InvalidDataLength + } + _, err = sd.tprot.next(int(size)) + return err +} + +func (sd *skipDecoder) skipMap(maxDepth int) error { + keyTypeId, valTypeId, size, err := sd.tprot.ReadMapBegin() + if err != nil { + return err + } + for i := 0; i < size; i++ { + if err = sd.skip(keyTypeId, maxDepth); err != nil { + return err + } + if err = sd.skip(valTypeId, maxDepth); err != nil { + return err + } + } + return nil +} + +func (sd *skipDecoder) skipList(maxDepth int) error { + elemTypeId, size, err := sd.tprot.ReadListBegin() + if err != nil { + return err + } + for i := 0; i < size; i++ { + if err = sd.skip(elemTypeId, maxDepth); err != nil { + return err + } + } + return nil +} + +func (sd *skipDecoder) skipSet(maxDepth int) error { + return sd.skipList(maxDepth) +} + +func (sd *skipDecoder) skip(typeId thrift.TType, maxDepth int) (err error) { + if maxDepth <= 0 { + return thrift.NewTProtocolExceptionWithType(thrift.DEPTH_LIMIT, errors.New("depth limit exceeded")) + } + + switch typeId { + case thrift.BOOL, thrift.BYTE: + if _, err = sd.tprot.next(1); err != nil { + return + } + case thrift.I16: + if _, err = sd.tprot.next(2); err != nil { + return + } + case thrift.I32: + if _, err = sd.tprot.next(4); err != nil { + return + } + case thrift.I64, thrift.DOUBLE: + if _, err = sd.tprot.next(8); err != nil { + return + } + case thrift.STRING: + if err = sd.skipString(); err != nil { + return + } + case thrift.STRUCT: + if err = sd.skipStruct(maxDepth - 1); err != nil { + return + } + case thrift.MAP: + if err = sd.skipMap(maxDepth - 1); err != nil { + return + } + case thrift.SET: + if err = sd.skipSet(maxDepth - 1); err != nil { + return + } + case thrift.LIST: + if err = sd.skipList(maxDepth - 1); err != nil { + return + } + default: + return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("unknown data type %d", typeId)) + } + return nil +} + +func (sd *skipDecoder) skipStruct(maxDepth int) (err error) { + var fieldTypeId thrift.TType + + for { + _, fieldTypeId, _, err = sd.tprot.ReadFieldBegin() + if err != nil { + return err + } + if fieldTypeId == thrift.STOP { + return err + } + if err = sd.skip(fieldTypeId, maxDepth); err != nil { + return err + } + } +} + +// Buffer returns the skipped buffer. +// Using this buffer to feed to Frugal or FastCodec +func (sd *skipDecoder) Buffer() ([]byte, error) { + return sd.sb.Buffer() +} + +// Recycle recycles the internal BinaryProtocol and would not affect the buffer +// returned by Buffer() +func (sd *skipDecoder) Recycle() { + sd.tprot.Recycle() +} diff --git a/pkg/remote/codec/thrift/skip_decoder_test.go b/pkg/remote/codec/thrift/skip_decoder_test.go new file mode 100644 index 0000000000..d80cfc05d5 --- /dev/null +++ b/pkg/remote/codec/thrift/skip_decoder_test.go @@ -0,0 +1,111 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package thrift + +import ( + "testing" + + "github.com/apache/thrift/lib/go/thrift" + + "github.com/cloudwego/kitex/internal/test" + "github.com/cloudwego/kitex/pkg/remote" +) + +func TestSkipBuffer(t *testing.T) { + buf := []byte{1, 2, 3} + rb := remote.NewReaderBuffer(buf) + sb := newSkipBuffer(rb) + peekData, err := sb.Next(3) + test.Assert(t, err == nil) + test.DeepEqual(t, buf, peekData[:len(buf)]) + test.Assert(t, sb.readNum == len(buf)) + test.Assert(t, rb.ReadLen() == 0) + + data, err := sb.Buffer() + test.Assert(t, err == nil) + test.DeepEqual(t, buf, data[:len(buf)]) + test.Assert(t, rb.ReadLen() == len(buf)) +} + +func TestSkipDecoder_SkipStruct(t *testing.T) { + tProt := NewBinaryProtocol(remote.NewReaderWriterBuffer(1024)) + defer tProt.Recycle() + tProt.WriteStructBegin("testStruct") + // 1. Byte + tProt.WriteFieldBegin("Byte", thrift.BYTE, 1) + tProt.WriteByte('1') + tProt.WriteFieldEnd() + // 2. Bool + tProt.WriteFieldBegin("Bool", thrift.BOOL, 2) + tProt.WriteBool(true) + tProt.WriteFieldEnd() + // 3. I16 + tProt.WriteFieldBegin("I16", thrift.I16, 3) + tProt.WriteI16(2) + tProt.WriteFieldEnd() + // 4. I32 + tProt.WriteFieldBegin("I32", thrift.I32, 4) + tProt.WriteI32(3) + tProt.WriteFieldEnd() + // 5. I64 + tProt.WriteFieldBegin("I64", thrift.I64, 5) + tProt.WriteI64(4) + tProt.WriteFieldEnd() + // 6. Double + tProt.WriteFieldBegin("Double", thrift.DOUBLE, 6) + tProt.WriteDouble(5) + tProt.WriteFieldEnd() + // 7. String + tProt.WriteFieldBegin("String", thrift.STRING, 7) + tProt.WriteString("6") + tProt.WriteFieldEnd() + // 8. Map + tProt.WriteFieldBegin("Map", thrift.MAP, 8) + tProt.WriteMapBegin(thrift.I32, thrift.I32, 1) + tProt.WriteI32(7) + tProt.WriteI32(8) + tProt.WriteMapEnd() + tProt.WriteFieldEnd() + // 9. Set + tProt.WriteFieldBegin("Set", thrift.SET, 9) + tProt.WriteSetBegin(thrift.I32, 1) + tProt.WriteI32(9) + tProt.WriteSetEnd() + tProt.WriteFieldEnd() + // 10. List + tProt.WriteFieldBegin("List", thrift.LIST, 10) + tProt.WriteListBegin(thrift.I32, 1) + tProt.WriteI32(9) + tProt.WriteListEnd() + tProt.WriteFieldEnd() + + tProt.WriteFieldStop() + tProt.WriteStructEnd() + + length := tProt.ByteBuffer().ReadableLen() + sd := newSkipDecoder(tProt.ByteBuffer()) + defer sd.Recycle() + err := sd.SkipStruct() + test.Assert(t, err == nil) + test.Assert(t, sd.sb.readNum == length) + test.Assert(t, sd.sb.ReadLen() == 0) + test.Assert(t, sd.sb.ReadableLen() == length) + _, err = sd.Buffer() + test.Assert(t, err == nil) + test.Assert(t, sd.sb.ReadLen() == length) + test.Assert(t, sd.sb.ReadableLen() == 0) +} diff --git a/pkg/remote/codec/thrift/thrift_data.go b/pkg/remote/codec/thrift/thrift_data.go index 221595ee94..6ec0d08fce 100644 --- a/pkg/remote/codec/thrift/thrift_data.go +++ b/pkg/remote/codec/thrift/thrift_data.go @@ -137,29 +137,58 @@ func UnmarshalThriftData(ctx context.Context, codec remote.PayloadCodec, method return err } +func (c thriftCodec) fastMessageUnmarshalEnabled() bool { + return c.CodecType&FastRead != 0 +} + +func (c thriftCodec) fastMessageUnmarshalAvailable(data interface{}, payloadLen int) bool { + if payloadLen == 0 && c.CodecType&EnableSkipDecoder == 0 { + return false + } + _, ok := data.(ThriftMsgFastCodec) + return ok +} + +func (c thriftCodec) fastUnmarshal(tProt *BinaryProtocol, data interface{}, dataLen int) error { + msg := data.(ThriftMsgFastCodec) + if dataLen > 0 { + buf, err := tProt.next(dataLen) + if err != nil { + return remote.NewTransError(remote.ProtocolError, err) + } + _, err = msg.FastRead(buf) + if err != nil { + return remote.NewTransError(remote.ProtocolError, err) + } + return nil + } + buf, err := getSkippedStructBuffer(tProt) + if err != nil { + return err + } + _, err = msg.FastRead(buf) + if err != nil { + return remote.NewTransError(remote.ProtocolError, err).AppendMessage("caught in FastCodec using SkipDecoder Buffer") + } + return err +} + // unmarshalThriftData only decodes the data (after methodName, msgType and seqId) // method is only used for generic calls func (c thriftCodec) unmarshalThriftData(ctx context.Context, tProt *BinaryProtocol, method string, data interface{}, dataLen int) error { // decode with hyper unmarshal - if c.hyperMessageUnmarshalEnabled() && hyperMessageUnmarshalAvailable(data, dataLen) { + if c.hyperMessageUnmarshalEnabled() && c.hyperMessageUnmarshalAvailable(data, dataLen) { return c.hyperUnmarshal(tProt, data, dataLen) } // decode with FastRead - if c.CodecType&FastRead != 0 { - if msg, ok := data.(ThriftMsgFastCodec); ok && dataLen > 0 { - buf, err := tProt.next(dataLen) - if err != nil { - return remote.NewTransError(remote.ProtocolError, err) - } - _, err = msg.FastRead(buf) - return err - } + if c.fastMessageUnmarshalEnabled() && c.fastMessageUnmarshalAvailable(data, dataLen) { + return c.fastUnmarshal(tProt, data, dataLen) } if err := verifyUnmarshalBasicThriftDataType(data); err != nil { // Basic can be used for disabling frugal, we need to check it - if c.CodecType != Basic && hyperMessageUnmarshalAvailable(data, dataLen) { + if c.CodecType != Basic && c.hyperMessageUnmarshalAvailable(data, dataLen) { // fallback to frugal when the generated code is using slim template return c.hyperUnmarshal(tProt, data, dataLen) } @@ -171,11 +200,25 @@ func (c thriftCodec) unmarshalThriftData(ctx context.Context, tProt *BinaryProto } func (c thriftCodec) hyperUnmarshal(tProt *BinaryProtocol, data interface{}, dataLen int) error { - buf, err := tProt.next(dataLen - bthrift.Binary.MessageEndLength()) + if dataLen > 0 { + buf, err := tProt.next(dataLen - bthrift.Binary.MessageEndLength()) + if err != nil { + return remote.NewTransError(remote.ProtocolError, err) + } + if err = c.hyperMessageUnmarshal(buf, data); err != nil { + return remote.NewTransError(remote.ProtocolError, err) + } + return nil + } + buf, err := getSkippedStructBuffer(tProt) if err != nil { - return remote.NewTransError(remote.ProtocolError, err) + return err } - return c.hyperMessageUnmarshal(buf, data) + if err = c.hyperMessageUnmarshal(buf, data); err != nil { + return remote.NewTransError(remote.ProtocolError, err).AppendMessage("caught in Frugal using SkipDecoder Buffer") + } + + return nil } // verifyUnmarshalBasicThriftDataType verifies whether data could be unmarshal by old thrift way @@ -207,3 +250,16 @@ func decodeBasicThriftData(ctx context.Context, tProt thrift.TProtocol, method s } return nil } + +func getSkippedStructBuffer(tProt *BinaryProtocol) ([]byte, error) { + sd := newSkipDecoder(tProt.trans) + defer sd.Recycle() + if err := sd.SkipStruct(); err != nil { + return nil, remote.NewTransError(remote.ProtocolError, err).AppendMessage("caught in SkipDecoder SkipStruct phase") + } + buf, err := sd.Buffer() + if err != nil { + return nil, remote.NewTransError(remote.ProtocolError, err).AppendMessage("caught in SkipDecoder Buffer phase") + } + return buf, nil +} diff --git a/pkg/remote/codec/thrift/thrift_data_test.go b/pkg/remote/codec/thrift/thrift_data_test.go index e3b69ca56d..d56cf0b55a 100644 --- a/pkg/remote/codec/thrift/thrift_data_test.go +++ b/pkg/remote/codec/thrift/thrift_data_test.go @@ -19,6 +19,7 @@ package thrift import ( "context" "reflect" + "strings" "testing" "github.com/apache/thrift/lib/go/thrift" @@ -121,6 +122,43 @@ func TestUnmarshalThriftData(t *testing.T) { // FrugalCodec: in thrift_frugal_amd64_test.go: TestUnmarshalThriftDataFrugal } +func TestThriftCodec_unmarshalThriftData(t *testing.T) { + t.Run("FastCodec with SkipDecoder enabled", func(t *testing.T) { + req := &fast.MockReq{} + codec := &thriftCodec{FastRead | EnableSkipDecoder} + tProt := NewBinaryProtocol(remote.NewReaderBuffer(mockReqThrift)) + defer tProt.Recycle() + // specify dataLen with 0 so that skipDecoder works + err := codec.unmarshalThriftData(context.Background(), tProt, "mock", req, 0) + checkDecodeResult(t, err, &fast.MockReq{ + Msg: req.Msg, + StrList: req.StrList, + StrMap: req.StrMap, + }) + }) + + t.Run("FastCodec with SkipDecoder enabled, failed in using SkipDecoder Buffer", func(t *testing.T) { + req := &fast.MockReq{} + codec := &thriftCodec{FastRead | EnableSkipDecoder} + // these bytes are mapped to + // Msg string `thrift:"Msg,1" json:"Msg"` + // StrMap map[string]string `thrift:"strMap,2" json:"strMap"` + // I16List []int16 `thrift:"I16List,3" json:"i16List"` + faultMockReqThrift := []byte{ + 11 /* string */, 0, 1 /* id=1 */, 0, 0, 0, 5 /* length=5 */, 104, 101, 108, 108, 111, /* "hello" */ + 13 /* map */, 0, 2 /* id=2 */, 11 /* key:string*/, 11 /* value:string */, 0, 0, 0, 0, /* map size=0 */ + 15 /* list */, 0, 3 /* id=3 */, 6 /* item:I16 */, 0, 0, 0, 1 /* length=1 */, 0, 1, /* I16=1 */ + 0, /* end of struct */ + } + tProt := NewBinaryProtocol(remote.NewReaderBuffer(faultMockReqThrift)) + defer tProt.Recycle() + // specify dataLen with 0 so that skipDecoder works + err := codec.unmarshalThriftData(context.Background(), tProt, "mock", req, 0) + test.Assert(t, err != nil, err) + test.Assert(t, strings.Contains(err.Error(), "caught in FastCodec using SkipDecoder Buffer")) + }) +} + func TestUnmarshalThriftException(t *testing.T) { // prepare exception thrift binary transport := thrift.NewTMemoryBufferLen(marshalThriftBufferSize) @@ -150,3 +188,14 @@ func Test_verifyUnmarshalBasicThriftDataType(t *testing.T) { test.Assert(t, err == nil, err) // data that is not part of basic thrift: in thrift_frugal_amd64_test.go: Test_verifyUnmarshalThriftDataFrugal } + +func Test_getSkippedStructBuffer(t *testing.T) { + // string length is 6 but only got "hello" + faultThrift := []byte{ + 11 /* string */, 0, 1 /* id=1 */, 0, 0, 0, 6 /* length=6 */, 104, 101, 108, 108, 111, /* "hello" */ + } + tProt := NewBinaryProtocol(remote.NewReaderBuffer(faultThrift)) + _, err := getSkippedStructBuffer(tProt) + test.Assert(t, err != nil, err) + test.Assert(t, strings.Contains(err.Error(), "caught in SkipDecoder SkipStruct phase")) +} diff --git a/pkg/remote/codec/thrift/thrift_frugal.go b/pkg/remote/codec/thrift/thrift_frugal.go index 36effa2d74..3c8a03ceba 100644 --- a/pkg/remote/codec/thrift/thrift_frugal.go +++ b/pkg/remote/codec/thrift/thrift_frugal.go @@ -64,8 +64,8 @@ func (c thriftCodec) hyperMessageUnmarshalEnabled() bool { } // hyperMessageUnmarshalAvailable indicates that if high priority message codec is available. -func hyperMessageUnmarshalAvailable(data interface{}, payloadLen int) bool { - if payloadLen == 0 { +func (c thriftCodec) hyperMessageUnmarshalAvailable(data interface{}, payloadLen int) bool { + if payloadLen == 0 && c.CodecType&EnableSkipDecoder == 0 { return false } dt := reflect.TypeOf(data).Elem() @@ -109,7 +109,7 @@ func (c thriftCodec) hyperMarshalBody(data interface{}) (buf []byte, err error) func (c thriftCodec) hyperMessageUnmarshal(buf []byte, data interface{}) error { _, err := frugal.DecodeObject(buf, data) if err != nil { - return remote.NewTransError(remote.ProtocolError, err) + return err } return nil } diff --git a/pkg/remote/codec/thrift/thrift_frugal_test.go b/pkg/remote/codec/thrift/thrift_frugal_test.go index b7e0eacd13..2d2134a240 100644 --- a/pkg/remote/codec/thrift/thrift_frugal_test.go +++ b/pkg/remote/codec/thrift/thrift_frugal_test.go @@ -26,6 +26,7 @@ package thrift import ( "context" "reflect" + "strings" "testing" "github.com/cloudwego/kitex/internal/mocks/thrift/fast" @@ -98,10 +99,10 @@ func TestHyperCodecCheck(t *testing.T) { // test hyperMessageUnmarshal check codec = &thriftCodec{FrugalRead} - test.Assert(t, hyperMessageUnmarshalAvailable(&MockNoTagArgs{}, msg.PayloadLen()) == false) - test.Assert(t, hyperMessageUnmarshalAvailable(&MockFrugalTagArgs{}, msg.PayloadLen()) == false) + test.Assert(t, codec.hyperMessageUnmarshalAvailable(&MockNoTagArgs{}, msg.PayloadLen()) == false) + test.Assert(t, codec.hyperMessageUnmarshalAvailable(&MockFrugalTagArgs{}, msg.PayloadLen()) == false) msg.SetPayloadLen(1) - test.Assert(t, hyperMessageUnmarshalAvailable(&MockFrugalTagArgs{}, msg.PayloadLen()) == true) + test.Assert(t, codec.hyperMessageUnmarshalAvailable(&MockFrugalTagArgs{}, msg.PayloadLen()) == true) } func TestFrugalCodec(t *testing.T) { @@ -215,6 +216,43 @@ func TestUnmarshalThriftDataFrugal(t *testing.T) { test.Assert(t, err != nil, err) } +func TestThriftCodec_unmarshalThriftDataFrugal(t *testing.T) { + t.Run("Frugal with SkipDecoder enabled", func(t *testing.T) { + req := &MockFrugalTagReq{} + codec := &thriftCodec{FrugalRead | EnableSkipDecoder} + tProt := NewBinaryProtocol(remote.NewReaderBuffer(mockReqThrift)) + defer tProt.Recycle() + // specify dataLen with 0 so that skipDecoder works + err := codec.unmarshalThriftData(context.Background(), tProt, "mock", req, 0) + checkDecodeResult(t, err, &fast.MockReq{ + Msg: req.Msg, + StrList: req.StrList, + StrMap: req.StrMap, + }) + }) + + t.Run("Frugal with SkipDecoder enabled, failed in using SkipDecoder Buffer", func(t *testing.T) { + req := &MockFrugalTagReq{} + codec := &thriftCodec{FrugalRead | EnableSkipDecoder} + // these bytes are mapped to + // Msg string `thrift:"Msg,1" json:"Msg"` + // StrMap map[string]string `thrift:"strMap,2" json:"strMap"` + // I16List []int16 `thrift:"I16List,3" json:"i16List"` + faultMockReqThrift := []byte{ + 11 /* string */, 0, 1 /* id=1 */, 0, 0, 0, 5 /* length=5 */, 104, 101, 108, 108, 111, /* "hello" */ + 13 /* map */, 0, 2 /* id=2 */, 11 /* key:string*/, 11 /* value:string */, 0, 0, 0, 0, /* map size=0 */ + 15 /* list */, 0, 3 /* id=3 */, 6 /* item:I16 */, 0, 0, 0, 1 /* length=1 */, 0, 1, /* I16=1 */ + 0, /* end of struct */ + } + tProt := NewBinaryProtocol(remote.NewReaderBuffer(faultMockReqThrift)) + defer tProt.Recycle() + // specify dataLen with 0 so that skipDecoder works + err := codec.unmarshalThriftData(context.Background(), tProt, "mock", req, 0) + test.Assert(t, err != nil, err) + test.Assert(t, strings.Contains(err.Error(), "caught in Frugal using SkipDecoder Buffer")) + }) +} + func Test_verifyMarshalThriftDataFrugal(t *testing.T) { err := verifyMarshalBasicThriftDataType(&MockFrugalTagArgs{}) test.Assert(t, err == errEncodeMismatchMsgType, err) diff --git a/pkg/remote/codec/thrift/thrift_others.go b/pkg/remote/codec/thrift/thrift_others.go index 344f5026d5..27a723cc1a 100644 --- a/pkg/remote/codec/thrift/thrift_others.go +++ b/pkg/remote/codec/thrift/thrift_others.go @@ -39,7 +39,7 @@ func (c thriftCodec) hyperMessageUnmarshalEnabled() bool { } // hyperMessageUnmarshalAvailable indicates that if high priority message codec is available. -func hyperMessageUnmarshalAvailable(data interface{}, payloadLen int) bool { +func (c thriftCodec) hyperMessageUnmarshalAvailable(data interface{}, payloadLen int) bool { return false } From d0c247b51c3c4b410fac18a96f94a66efc1415f5 Mon Sep 17 00:00:00 2001 From: Li2CO3 Date: Tue, 16 Apr 2024 17:25:01 +0800 Subject: [PATCH 08/49] optimize(tool): remove thrift processor for less codegen (#1326) --- tool/cmd/kitex/args/args.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tool/cmd/kitex/args/args.go b/tool/cmd/kitex/args/args.go index dfce9984cb..3f04295571 100644 --- a/tool/cmd/kitex/args/args.go +++ b/tool/cmd/kitex/args/args.go @@ -135,6 +135,7 @@ func (a *Arguments) buildFlags(version string) *flag.FlagSet { "compatible_names", "frugal_tag", "thrift_streaming", + "no_processor", ) for _, e := range a.extends { From bda9a0d1e74c874515479fc3a53d20c46b244924 Mon Sep 17 00:00:00 2001 From: Joway Date: Thu, 18 Apr 2024 16:10:52 +0800 Subject: [PATCH 09/49] perf: linear allocator for fast codec ReadString/ReadBinary (#1276) --- pkg/mem/span.go | 104 ++++++++++++++++++ pkg/mem/span_test.go | 163 ++++++++++++++++++++++++++++ pkg/protocol/bthrift/binary.go | 17 +-- pkg/protocol/bthrift/binary_test.go | 26 +++++ 4 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 pkg/mem/span.go create mode 100644 pkg/mem/span_test.go diff --git a/pkg/mem/span.go b/pkg/mem/span.go new file mode 100644 index 0000000000..687e27a2f0 --- /dev/null +++ b/pkg/mem/span.go @@ -0,0 +1,104 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mem + +import ( + "math/bits" + "sync/atomic" +) + +const ( + spanCacheSize = 10 + minSpanObject = 128 // 128 B + maxSpanObject = (minSpanObject << spanCacheSize) - 1 // 128 KB + minSpanClass = 8 // = spanClass(minSpanObject) +) + +type spanCache struct { + spans [spanCacheSize]*span +} + +func NewSpanCache(spanSize int) *spanCache { + c := new(spanCache) + for i := 0; i < len(c.spans); i++ { + c.spans[i] = NewSpan(spanSize) + } + return c +} + +func (c *spanCache) Make(n int) []byte { + sclass := spanClass(n) - minSpanClass + if sclass < 0 || sclass >= len(c.spans) { + return make([]byte, n) + } + return c.spans[sclass].Make(n) +} + +func (c *spanCache) Copy(buf []byte) (p []byte) { + p = c.Make(len(buf)) + copy(p, buf) + return p +} + +func NewSpan(size int) *span { + sp := new(span) + sp.size = uint32(size) + sp.buffer = make([]byte, 0, size) + return sp +} + +type span struct { + lock uint32 + read uint32 // read index of buffer + size uint32 // size of buffer + buffer []byte +} + +func (b *span) Make(_n int) []byte { + n := uint32(_n) + if n >= b.size || !atomic.CompareAndSwapUint32(&b.lock, 0, 1) { + // fallback path: make a new byte slice if current goroutine cannot get the lock or n is out of size + return make([]byte, n) + } +START: + b.read += n + // fast path + if b.read <= b.size { + buf := b.buffer[b.read-n : b.read] + atomic.StoreUint32(&b.lock, 0) + return buf + } + // slow path: create a new buffer + b.buffer = make([]byte, b.size) + b.read = 0 + goto START +} + +func (b *span) Copy(buf []byte) (p []byte) { + p = b.Make(len(buf)) + copy(p, buf) + return p +} + +// spanClass calc the minimum number of bits required to represent x +// [2^sclass,2^(sclass+1)) bytes in a same span class +func spanClass(size int) int { + if size == 0 { + return 0 + } + return bits.Len(uint(size)) +} diff --git a/pkg/mem/span_test.go b/pkg/mem/span_test.go new file mode 100644 index 0000000000..1ebafbdcfb --- /dev/null +++ b/pkg/mem/span_test.go @@ -0,0 +1,163 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mem + +import ( + "fmt" + "sync" + "testing" + + "github.com/cloudwego/kitex/internal/test" +) + +func TestSpanClass(t *testing.T) { + test.DeepEqual(t, spanClass(minSpanObject), minSpanClass) + + test.DeepEqual(t, spanClass(1), 1) + + test.DeepEqual(t, spanClass(2), 2) + test.DeepEqual(t, spanClass(3), 2) + + test.DeepEqual(t, spanClass(4), 3) + test.DeepEqual(t, spanClass(5), 3) + test.DeepEqual(t, spanClass(6), 3) + test.DeepEqual(t, spanClass(7), 3) + + test.DeepEqual(t, spanClass(8), 4) + test.DeepEqual(t, spanClass(9), 4) + + test.DeepEqual(t, spanClass(32), 6) + test.DeepEqual(t, spanClass(33), 6) + test.DeepEqual(t, spanClass(63), 6) + + test.DeepEqual(t, spanClass(64), 7) + test.DeepEqual(t, spanClass(65), 7) + test.DeepEqual(t, spanClass(127), 7) + test.DeepEqual(t, spanClass(128), 8) + test.DeepEqual(t, spanClass(129), 8) +} + +func TestSpan(t *testing.T) { + bc := NewSpan(1024 * 32) + + var wg sync.WaitGroup + for c := 0; c < 12; c++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 1024; i++ { + buf := []byte("123") + test.DeepEqual(t, bc.Copy(buf), buf) + + buf = make([]byte, 32) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 63) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 64) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 32*1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 64*1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 128*1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + } + }() + } + wg.Wait() +} + +func TestSpanCache(t *testing.T) { + bc := NewSpanCache(1024 * 32) + + var wg sync.WaitGroup + for c := 0; c < 12; c++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 1024; i++ { + buf := []byte("123") + test.DeepEqual(t, bc.Copy(buf), buf) + + buf = make([]byte, 32) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 63) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 64) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 32*1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 64*1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + buf = make([]byte, 128*1024) + test.DeepEqual(t, len(bc.Copy(buf)), len(buf)) + } + }() + } + wg.Wait() +} + +var benchStringSizes = []int{ + 15, 31, 127, // not cached + 128, 256, 1024 - 1, 4*1024 - 1, + 32*1024 - 1, 128*1024 - 1, + 512*1024 - 1, // not cached +} + +func BenchmarkSpanCacheCopy(b *testing.B) { + for _, sz := range benchStringSizes { + b.Run(fmt.Sprintf("size=%d", sz), func(b *testing.B) { + from := make([]byte, sz) + sc := NewSpanCache(maxSpanObject * 8) + b.RunParallel(func(pb *testing.PB) { + b.ReportAllocs() + b.ResetTimer() + var buffer []byte + for pb.Next() { + buffer = sc.Copy(from) + buffer[0] = 'a' + buffer[sz-1] = 'z' + } + _ = buffer + }) + }) + } +} + +func BenchmarkMakeAndCopy(b *testing.B) { + for _, sz := range benchStringSizes { + b.Run(fmt.Sprintf("size=%d", sz), func(b *testing.B) { + from := make([]byte, sz) + b.RunParallel(func(pb *testing.PB) { + b.ReportAllocs() + b.ResetTimer() + var buffer []byte + for pb.Next() { + buffer = make([]byte, sz) + copy(buffer, from) + buffer[0] = 'a' + buffer[sz-1] = 'z' + } + _ = buffer + }) + }) + } +} diff --git a/pkg/protocol/bthrift/binary.go b/pkg/protocol/bthrift/binary.go index 2e08813b03..fa0f6c4ae6 100644 --- a/pkg/protocol/bthrift/binary.go +++ b/pkg/protocol/bthrift/binary.go @@ -25,14 +25,17 @@ import ( "github.com/apache/thrift/lib/go/thrift" + "github.com/cloudwego/kitex/pkg/mem" "github.com/cloudwego/kitex/pkg/remote/codec/perrors" "github.com/cloudwego/kitex/pkg/utils" ) -// Binary protocol for bthrift. -var Binary binaryProtocol - -var _ BTProtocol = binaryProtocol{} +var ( + // Binary protocol for bthrift. + Binary binaryProtocol + _ BTProtocol = binaryProtocol{} + spanCache = mem.NewSpanCache(1024 * 1024) // 1MB +) type binaryProtocol struct{} @@ -458,7 +461,8 @@ func (binaryProtocol) ReadString(buf []byte) (value string, length int, err erro if size < 0 || int(size) > len(buf) { return value, length, perrors.NewProtocolErrorWithType(thrift.INVALID_DATA, "[ReadString] the string size greater than buf length") } - value = string(buf[length : length+int(size)]) + data := spanCache.Copy(buf[length : length+int(size)]) + value = utils.SliceByteToString(data) length += int(size) return } @@ -473,8 +477,7 @@ func (binaryProtocol) ReadBinary(buf []byte) (value []byte, length int, err erro if size < 0 || int(size) > len(buf) { return value, length, perrors.NewProtocolErrorWithType(thrift.INVALID_DATA, "[ReadBinary] the binary size greater than buf length") } - value = make([]byte, size) - copy(value, buf[length:length+int(size)]) + value = spanCache.Copy(buf[length : length+int(size)]) length += int(size) return } diff --git a/pkg/protocol/bthrift/binary_test.go b/pkg/protocol/bthrift/binary_test.go index 2fcdc11ffc..2e42b2defd 100644 --- a/pkg/protocol/bthrift/binary_test.go +++ b/pkg/protocol/bthrift/binary_test.go @@ -17,6 +17,7 @@ package bthrift import ( + "encoding/binary" "fmt" "testing" @@ -534,3 +535,28 @@ func TestSkip(t *testing.T) { _, valSet, _, _ := Binary.ReadSetBegin(buf[offset:]) test.Assert(t, valSet == 11) } + +func BenchmarkBinaryProtocolReadString(b *testing.B) { + stringSizes := []int{64, 128, 1024, 4096, 10240, 102400} + strings := 32 + for _, sz := range stringSizes { + b.Run(fmt.Sprintf("string_size=%d", sz), func(b *testing.B) { + stringBuffer := make([]byte, sz*strings+4*strings) + start := 0 + for i := 0; i < strings; i++ { + binary.BigEndian.PutUint32(stringBuffer[start:start+4], uint32(sz)) + start += sz + 4 + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for i := 0; i < strings; i++ { + _, l, err := Binary.ReadString(stringBuffer) + if l-4 != sz || err != nil { + b.Fatal("read string failed", l, err) + } + } + } + }) + } +} From d284d58d4b78beace2942d1e30a94594a04d3580 Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Thu, 18 Apr 2024 17:23:05 +0800 Subject: [PATCH 10/49] Allow custom registry to get the actual address for registering --- pkg/registry/registry.go | 2 ++ server/option.go | 15 +++++++++++++++ server/server.go | 6 ++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index aea9f087fe..637e3f0ad6 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -37,6 +37,8 @@ type Info struct { ServiceName string // Addr will be set in kitex by default Addr net.Addr + // SkipListenAddr is used to prevent the listen addr from overriding the Addr + SkipListenAddr bool // PayloadCodec will be set in kitex by default, like thrift, protobuf PayloadCodec string diff --git a/server/option.go b/server/option.go index 59e587d7cd..7f3dcadf79 100644 --- a/server/option.go +++ b/server/option.go @@ -232,6 +232,14 @@ func WithRegistryInfo(info *registry.Info) Option { }} } +func WithRegistryInfoAvoidListenAddr(skipListenAddr bool) Option { + return Option{F: func(o *internal_server.Options, di *utils.Slice) { + di.Push(fmt.Sprintf("WithRegistryInfoAvoidListenAddr(%+v)", skipListenAddr)) + + o.RegistryInfo.SkipListenAddr = skipListenAddr + }} +} + // WithGRPCWriteBufferSize determines how much data can be batched before doing a write on the wire. // The corresponding memory allocation for this buffer will be twice the size to keep syscalls low. // The default value for this buffer is 32KB. @@ -335,6 +343,13 @@ func WithGRPCUnknownServiceHandler(f func(ctx context.Context, methodName string }} } +func WithUnknownMethodHandler(f func(ctx context.Context, message remote.ByteBuffer) error) Option { + return Option{F: func(o *internal_server.Options, di *utils.Slice) { + di.Push(fmt.Sprintf("WithUnknownMethodHandler(%+v)", utils.GetFuncName(f))) + o.RemoteOpt.UnknownMethodHandler = f + }} +} + // Deprecated: Use WithConnectionLimiter instead. func WithConcurrencyLimiter(conLimit limiter.ConcurrencyLimiter) Option { return Option{F: func(o *internal_server.Options, di *utils.Slice) { diff --git a/server/server.go b/server/server.go index c5251de3d8..07027ede9e 100644 --- a/server/server.go +++ b/server/server.go @@ -495,8 +495,10 @@ func (s *server) buildRegistryInfo(lAddr net.Addr) { s.opt.RegistryInfo = ®istry.Info{} } info := s.opt.RegistryInfo - // notice: lAddr may be nil when listen failed - info.Addr = lAddr + if !info.SkipListenAddr { + // notice: lAddr may be nil when listen failed + info.Addr = lAddr + } if info.ServiceName == "" { info.ServiceName = s.opt.Svr.ServiceName } From 394a14a2444e29e17cb655c69a3f27a14d54684a Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Thu, 18 Apr 2024 17:23:33 +0800 Subject: [PATCH 11/49] Allow custom registry to get the actual address for registering --- server/server_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/server/server_test.go b/server/server_test.go index f932054cbc..27d8783be7 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -436,6 +436,49 @@ func TestServiceRegistryInfoWithNilTags(t *testing.T) { test.Assert(t, drCount == 1) } +func TestServiceRegistryInfoSkipListenAddr(t *testing.T) { + realAddr := utils.NewNetAddr("tcp", "127.0.0.1:9999") + registryInfo := registry.Info{ + Weight: 100, + Addr: realAddr, + SkipListenAddr: true, + } + checkInfo := func(info *registry.Info) { + test.Assert(t, info.Weight == registryInfo.Weight, info.Weight) + test.Assert(t, info.SkipListenAddr, info.SkipListenAddr) + test.DeepEqual(t, info.Addr, realAddr) + } + var rCount int + var drCount int + mockRegistry := MockRegistry{ + RegisterFunc: func(info *registry.Info) error { + checkInfo(info) + rCount++ + return nil + }, + DeregisterFunc: func(info *registry.Info) error { + checkInfo(info) + drCount++ + return nil + }, + } + var opts []Option + opts = append(opts, WithRegistry(mockRegistry)) + opts = append(opts, WithRegistryInfo(®istryInfo)) + svr := NewServer(opts...) + err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) + test.Assert(t, err == nil) + + time.AfterFunc(2000*time.Millisecond, func() { + err := svr.Stop() + test.Assert(t, err == nil, err) + }) + err = svr.Run() + test.Assert(t, err == nil, err) + test.Assert(t, rCount == 1) + test.Assert(t, drCount == 1) +} + func TestGRPCServerMultipleServices(t *testing.T) { var opts []Option opts = append(opts, withGRPCTransport()) From 7df08f21f60d2a8673925693b8409554b6ba9318 Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Thu, 18 Apr 2024 17:26:37 +0800 Subject: [PATCH 12/49] Allow custom registry to get the actual address for registering --- server/option.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/server/option.go b/server/option.go index 7f3dcadf79..0ad16e71f1 100644 --- a/server/option.go +++ b/server/option.go @@ -343,13 +343,6 @@ func WithGRPCUnknownServiceHandler(f func(ctx context.Context, methodName string }} } -func WithUnknownMethodHandler(f func(ctx context.Context, message remote.ByteBuffer) error) Option { - return Option{F: func(o *internal_server.Options, di *utils.Slice) { - di.Push(fmt.Sprintf("WithUnknownMethodHandler(%+v)", utils.GetFuncName(f))) - o.RemoteOpt.UnknownMethodHandler = f - }} -} - // Deprecated: Use WithConnectionLimiter instead. func WithConcurrencyLimiter(conLimit limiter.ConcurrencyLimiter) Option { return Option{F: func(o *internal_server.Options, di *utils.Slice) { From 5749adac040dc123b51dcb370e7adc2946b28153 Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Thu, 18 Apr 2024 17:34:16 +0800 Subject: [PATCH 13/49] Allow custom registry to get the actual address for registering --- server/option.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/option.go b/server/option.go index 0ad16e71f1..0d0a09f559 100644 --- a/server/option.go +++ b/server/option.go @@ -232,7 +232,7 @@ func WithRegistryInfo(info *registry.Info) Option { }} } -func WithRegistryInfoAvoidListenAddr(skipListenAddr bool) Option { +func WithRegistryInfoSkipListenAddr(skipListenAddr bool) Option { return Option{F: func(o *internal_server.Options, di *utils.Slice) { di.Push(fmt.Sprintf("WithRegistryInfoAvoidListenAddr(%+v)", skipListenAddr)) From 4bf857675a98121964c28a568d394787009c979f Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Fri, 19 Apr 2024 09:15:42 +0800 Subject: [PATCH 14/49] Delete WithRegistryInfoSkipListenAddr; Use test.GetLocalAddress();Minimize the time needed for test; Add TestServiceRegistryInfoSkipListenAddr --- server/option.go | 8 ------- server/server_test.go | 55 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/server/option.go b/server/option.go index 0d0a09f559..59e587d7cd 100644 --- a/server/option.go +++ b/server/option.go @@ -232,14 +232,6 @@ func WithRegistryInfo(info *registry.Info) Option { }} } -func WithRegistryInfoSkipListenAddr(skipListenAddr bool) Option { - return Option{F: func(o *internal_server.Options, di *utils.Slice) { - di.Push(fmt.Sprintf("WithRegistryInfoAvoidListenAddr(%+v)", skipListenAddr)) - - o.RegistryInfo.SkipListenAddr = skipListenAddr - }} -} - // WithGRPCWriteBufferSize determines how much data can be batched before doing a write on the wire. // The corresponding memory allocation for this buffer will be twice the size to keep syscalls low. // The default value for this buffer is 32KB. diff --git a/server/server_test.go b/server/server_test.go index 27d8783be7..d5500faba3 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -436,9 +436,9 @@ func TestServiceRegistryInfoWithNilTags(t *testing.T) { test.Assert(t, drCount == 1) } -func TestServiceRegistryInfoSkipListenAddr(t *testing.T) { - realAddr := utils.NewNetAddr("tcp", "127.0.0.1:9999") - registryInfo := registry.Info{ +func TestServiceRegistryInfoWithSkipListenAddr(t *testing.T) { + realAddr := utils.NewNetAddr("tcp", test.GetLocalAddress()) + registryInfo := ®istry.Info{ Weight: 100, Addr: realAddr, SkipListenAddr: true, @@ -446,7 +446,7 @@ func TestServiceRegistryInfoSkipListenAddr(t *testing.T) { checkInfo := func(info *registry.Info) { test.Assert(t, info.Weight == registryInfo.Weight, info.Weight) test.Assert(t, info.SkipListenAddr, info.SkipListenAddr) - test.DeepEqual(t, info.Addr, realAddr) + test.Assert(t, info.Addr.String() == realAddr.String(), info.Addr) } var rCount int var drCount int @@ -464,12 +464,55 @@ func TestServiceRegistryInfoSkipListenAddr(t *testing.T) { } var opts []Option opts = append(opts, WithRegistry(mockRegistry)) - opts = append(opts, WithRegistryInfo(®istryInfo)) + opts = append(opts, WithRegistryInfo(registryInfo)) svr := NewServer(opts...) err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) test.Assert(t, err == nil) - time.AfterFunc(2000*time.Millisecond, func() { + time.AfterFunc(time.Second, func() { + err := svr.Stop() + test.Assert(t, err == nil, err) + }) + err = svr.Run() + test.Assert(t, err == nil, err) + test.Assert(t, rCount == 1) + test.Assert(t, drCount == 1) +} + +func TestServiceRegistryInfoWithoutSkipListenAddr(t *testing.T) { + realAddr := utils.NewNetAddr("tcp", test.GetLocalAddress()) + println(realAddr) + registryInfo := ®istry.Info{ + Weight: 100, + Addr: realAddr, + } + checkInfo := func(info *registry.Info) { + test.Assert(t, info.Weight == registryInfo.Weight, info.Weight) + test.Assert(t, !info.SkipListenAddr, info.SkipListenAddr) + test.Assert(t, info.Addr.String() != realAddr.String(), info.Addr) + } + var rCount int + var drCount int + mockRegistry := MockRegistry{ + RegisterFunc: func(info *registry.Info) error { + checkInfo(info) + rCount++ + return nil + }, + DeregisterFunc: func(info *registry.Info) error { + checkInfo(info) + drCount++ + return nil + }, + } + var opts []Option + opts = append(opts, WithRegistry(mockRegistry)) + opts = append(opts, WithRegistryInfo(registryInfo)) + svr := NewServer(opts...) + err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) + test.Assert(t, err == nil) + + time.AfterFunc(time.Second, func() { err := svr.Stop() test.Assert(t, err == nil, err) }) From e6418fca3ce7f45b168a940560f759d56bace310 Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Fri, 19 Apr 2024 13:22:40 +0800 Subject: [PATCH 15/49] chore: update dynamicgo and sonic version (#1324) --- go.mod | 6 ++--- go.sum | 72 ++++++++++++---------------------------------------------- 2 files changed, 17 insertions(+), 61 deletions(-) diff --git a/go.mod b/go.mod index 37ccd6878c..1e307876ba 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/apache/thrift v0.13.0 github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b github.com/bytedance/mockey v1.2.7 - github.com/bytedance/sonic v1.11.2 + github.com/bytedance/sonic v1.11.5 github.com/choleraehyq/pid v0.0.18 - github.com/cloudwego/configmanager v0.2.0 - github.com/cloudwego/dynamicgo v0.2.0 + github.com/cloudwego/configmanager v0.2.1 + github.com/cloudwego/dynamicgo v0.2.2 github.com/cloudwego/fastpb v0.0.4 github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 diff --git a/go.sum b/go.sum index 31e189be23..160a74075e 100644 --- a/go.sum +++ b/go.sum @@ -12,67 +12,41 @@ github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/brianvoe/gofakeit/v6 v6.16.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= -github.com/bytedance/gopkg v0.0.0-20220509134931-d1878f638986/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= -github.com/bytedance/gopkg v0.0.0-20220531084716-665b4f21126f/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= -github.com/bytedance/gopkg v0.0.0-20230531144706-a12972768317/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b h1:R6PWoQtxEMpWJPHnpci+9LgFxCS7iJCfOGBvCgZeTKI= github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= -github.com/bytedance/mockey v1.2.0/go.mod h1:+Jm/fzWZAuhEDrPXVjDf/jLM2BlLXJkwk94zf2JZ3X4= github.com/bytedance/mockey v1.2.7 h1:8j4yCqS5OmMe2dQCxPit4FVkwTK9nrykIgbOZN3s28o= github.com/bytedance/mockey v1.2.7/go.mod h1:bNrUnI1u7+pAc0TYDgPATM+wF2yzHxmNH+iDXg4AOCU= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= -github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.5 h1:G00FYjjqll5iQ1PYXynbg/hyzqBqavH8Mo9/oTopd9k= +github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw= +github.com/bytedance/sonic/loader v0.1.0 h1:skjHJ2Bi9ibbq3Dwzh1w42MQ7wZJrXmEZr/uqUn3f0Q= +github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.0.0-20220818063314-28c361dae733/go.mod h1:wOQ0nsbeOLa2awv8bUYFW/EHXbjQMlZ10fAlXDB2sz8= -github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/choleraehyq/pid v0.0.13/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= -github.com/choleraehyq/pid v0.0.15/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= -github.com/choleraehyq/pid v0.0.16/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= github.com/choleraehyq/pid v0.0.18 h1:O7LLxPoOyt3YtonlCC8BmNrF9P6Hc8B509UOqlPSVhw= github.com/choleraehyq/pid v0.0.18/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/configmanager v0.2.0 h1:niVpVg+wQ+npNqnH3dup96SMbR02Pk+tNErubYCJqKo= -github.com/cloudwego/configmanager v0.2.0/go.mod h1:FLIQTjxsZRGjnmDhTttWQTy6f6DghPTatfBVOs2gQLk= -github.com/cloudwego/dynamicgo v0.1.0/go.mod h1:Mdsz0XGsIImi15vxhZaHZpspNChEmBMIiWkUfD6JDKg= -github.com/cloudwego/dynamicgo v0.2.0 h1:2mIqwYjS4TvjIov+dV5/y4OO33x/YMdfaeiRgXiineg= -github.com/cloudwego/dynamicgo v0.2.0/go.mod h1:zTbRLRyBdP+OLalvkiwWPnvg84v1UungzT7iuL/2Qgc= -github.com/cloudwego/fastpb v0.0.3/go.mod h1:/V13XFTq2TUkxj2qWReV8MwfPC4NnPcy6FsrojnsSG0= +github.com/cloudwego/base64x v0.1.3 h1:b5J/l8xolB7dyDTTmhJP2oTs5LdrjyrUFuNxdfq5hAg= +github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1umPV+aN8pi8= +github.com/cloudwego/configmanager v0.2.1 h1:JRmM9HPl8IafjrUU5waNboYjhiF3H7HY5oKuyoiiZto= +github.com/cloudwego/configmanager v0.2.1/go.mod h1:0oD/BWaTuznBOawVeTmTl3LE99RWaw7rX2jECowmY58= +github.com/cloudwego/dynamicgo v0.2.2-dep3/go.mod h1:oFLzd9SEUtU7XbSc7AT9e5xoAV1OJ1mVpudtUOiD7PQ= +github.com/cloudwego/dynamicgo v0.2.2 h1:jhh8Zo0VkUsI3IrdAHvuTrmslDhZ8tH+3WqYZcgykt8= +github.com/cloudwego/dynamicgo v0.2.2/go.mod h1:k840iCFH9ng9PBqr6jIoOyZxdk58EPEccrbfOk4ni1s= github.com/cloudwego/fastpb v0.0.4 h1:/ROVVfoFtpfc+1pkQLzGs+azjxUbSOsAqSY4tAAx4mg= github.com/cloudwego/fastpb v0.0.4/go.mod h1:/V13XFTq2TUkxj2qWReV8MwfPC4NnPcy6FsrojnsSG0= -github.com/cloudwego/frugal v0.1.3/go.mod h1:b981ViPYdhI56aFYsoMjl9kv6yeqYSO+iEz2jrhkCgI= -github.com/cloudwego/frugal v0.1.6/go.mod h1:9ElktKsh5qd2zDBQ5ENhPSQV7F2dZ/mXlr1eaZGDBFs= github.com/cloudwego/frugal v0.1.15 h1:LC55UJKhQPMFVjDPbE+LJcF7etZjSx6uokG1tk0wPK0= github.com/cloudwego/frugal v0.1.15/go.mod h1:26kU1r18vA8vRg12c66XPDlfv1GQHDbE1RpusipXfcI= -github.com/cloudwego/iasm v0.0.9 h1:DgNtfPjuz3YAQ0hmmiGg6DkDGj+foARFSwu7vKFPT1o= github.com/cloudwego/iasm v0.0.9/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/cloudwego/kitex v0.3.2/go.mod h1:/XD07VpUD9VQWmmoepASgZ6iw//vgWikVA9MpzLC5i0= -github.com/cloudwego/kitex v0.4.4/go.mod h1:3FcH5h9Qw+dhRljSzuGSpWuThttA8DvK0BsL7HUYydo= -github.com/cloudwego/kitex v0.6.1/go.mod h1:zI1GBrjT0qloTikcCfQTgxg3Ws+yQMyaChEEOcGNUvA= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cloudwego/kitex v0.9.3-dep3/go.mod h1:87pfgsZMaso0tFISQZzMhwmh6pajfVmw/R3COsh2pCg= github.com/cloudwego/localsession v0.0.2 h1:N9/IDtCPj1fCL9bCTP+DbXx3f40YjVYWcwkJG0YhQkY= github.com/cloudwego/localsession v0.0.2/go.mod h1:kiJxmvAcy4PLgKtEnPS5AXed3xCiXcs7Z+KBHP72Wv8= -github.com/cloudwego/netpoll v0.2.4/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E= -github.com/cloudwego/netpoll v0.3.1/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E= -github.com/cloudwego/netpoll v0.4.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= github.com/cloudwego/netpoll v0.6.0 h1:JRMkrA1o8k/4quxzg6Q1XM+zIhwZsyoWlq6ef+ht31U= github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= -github.com/cloudwego/thriftgo v0.1.2/go.mod h1:LzeafuLSiHA9JTiWC8TIMIq64iadeObgRUhmVG1OC/w= -github.com/cloudwego/thriftgo v0.2.4/go.mod h1:8i9AF5uDdWHGqzUhXDlubCjx4MEfKvWXGQlMWyR0tM4= -github.com/cloudwego/thriftgo v0.2.7/go.mod h1:8i9AF5uDdWHGqzUhXDlubCjx4MEfKvWXGQlMWyR0tM4= github.com/cloudwego/thriftgo v0.2.11/go.mod h1:dAyXHEmKXo0LfMCrblVEY3mUZsdeuA5+i0vF5f09j7E= github.com/cloudwego/thriftgo v0.3.6 h1:gHHW8Ag3cAEQ/awP4emTJiRPr5yQjbANhcsmV8/Epbw= github.com/cloudwego/thriftgo v0.3.6/go.mod h1:29ukiySoAMd0vXMYIduAY9dph/7dmChvOS11YLotFb8= @@ -140,7 +114,6 @@ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47 github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jhump/protoreflect v1.8.2 h1:k2xE7wcUomeqwY0LDCYA16y4WWfyTcMx5mKhk0d4ua0= github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -150,7 +123,6 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= @@ -159,12 +131,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/gls v0.0.0-20220109145502-612d0167dce5 h1:uiS4zKYKJVj5F3ID+5iylfKPsEQmBEOucSD9Vgmn0i0= github.com/modern-go/gls v0.0.0-20220109145502-612d0167dce5/go.mod h1:I8AX+yW//L8Hshx6+a1m3bYkwXkpsVjA2795vP4f4oQ= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= @@ -178,7 +148,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= @@ -192,14 +161,12 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/thrift-iterator/go v0.0.0-20190402154806-9b5a67519118/go.mod h1:60PRwE/TCI1UqLvn8v2pwAf6+yzTPLP/Ji5xaesWDqk= github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -208,9 +175,6 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/v2pro/plz v0.0.0-20221028024117-e5f9aec5b631/go.mod h1:3gacX+hQo+xvl0vtLqCMufzxuNCwt4geAVOMt2LQYfE= -github.com/v2pro/quokka v0.0.0-20171201153428-382cb39c6ee6/go.mod h1:0VP5W9AFNVWU8C1QLNeVg8TvzoEkIHWZ4vxtxEVFWUY= -github.com/v2pro/wombat v0.0.0-20180402055224-a56dbdcddef2/go.mod h1:wen8nMxrRrUmXnRwH+3wGAW+hyYTHcOrTNhMpxyp/i0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -219,7 +183,6 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.0.0-20220722155209-00200b7164a7/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -274,7 +237,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -297,7 +259,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -309,7 +270,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -317,7 +277,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= @@ -404,11 +363,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 539bffcc9788f64a10cbc087b17b7ea054626f87 Mon Sep 17 00:00:00 2001 From: QihengZhou Date: Fri, 19 Apr 2024 15:03:55 +0800 Subject: [PATCH 16/49] fix(gRPC): release connection in DoFinish for grpc streaming to close the short connection (#1328) --- client/stream.go | 16 ++-- client/stream_test.go | 89 +++++++++++++++++----- internal/mocks/remote/conn_wrapper.go | 64 ++++++++++++++++ internal/mocks/update.sh | 1 + pkg/remote/remotecli/conn_wrapper.go | 7 ++ pkg/remote/remotecli/stream.go | 23 +++++- pkg/remote/remotecli/stream_test.go | 15 +++- pkg/remote/trans/nphttp2/conn_pool.go | 3 + pkg/remote/trans/nphttp2/conn_pool_test.go | 53 +++++++++++++ pkg/remote/trans/nphttp2/mocks_test.go | 6 ++ 10 files changed, 248 insertions(+), 29 deletions(-) create mode 100644 internal/mocks/remote/conn_wrapper.go diff --git a/client/stream.go b/client/stream.go index 1b22a96652..a184ac7ecc 100644 --- a/client/stream.go +++ b/client/stream.go @@ -87,11 +87,11 @@ func (kc *kClient) invokeStreamingEndpoint() (endpoint.Endpoint, error) { return func(ctx context.Context, req, resp interface{}) (err error) { // req and resp as &streaming.Stream ri := rpcinfo.GetRPCInfo(ctx) - st, err := remotecli.NewStream(ctx, ri, handler, kc.opt.RemoteOpt) + st, scm, err := remotecli.NewStream(ctx, ri, handler, kc.opt.RemoteOpt) if err != nil { return } - clientStream := newStream(st, kc, ri, kc.getStreamingMode(ri), sendEndpoint, recvEndpoint) + clientStream := newStream(st, scm, kc, ri, kc.getStreamingMode(ri), sendEndpoint, recvEndpoint) resp.(*streaming.Result).Stream = clientStream return }, nil @@ -107,6 +107,7 @@ func (kc *kClient) getStreamingMode(ri rpcinfo.RPCInfo) serviceinfo.StreamingMod type stream struct { stream streaming.Stream + scm *remotecli.StreamConnManager kc *kClient ri rpcinfo.RPCInfo @@ -118,11 +119,12 @@ type stream struct { var _ streaming.WithDoFinish = (*stream)(nil) -func newStream(s streaming.Stream, kc *kClient, ri rpcinfo.RPCInfo, +func newStream(s streaming.Stream, scm *remotecli.StreamConnManager, kc *kClient, ri rpcinfo.RPCInfo, mode serviceinfo.StreamingMode, sendEP endpoint.SendEndpoint, recvEP endpoint.RecvEndpoint, ) *stream { return &stream{ stream: s, + scm: scm, kc: kc, ri: ri, streamingMode: mode, @@ -191,6 +193,7 @@ func (s *stream) Close() error { } // DoFinish implements the streaming.WithDoFinish interface, and it records the end of stream +// It will release the connection. func (s *stream) DoFinish(err error) { if atomic.SwapUint32(&s.finished, 1) == 1 { // already called @@ -200,9 +203,10 @@ func (s *stream) DoFinish(err error) { // only rpc errors are reported err = nil } - ctx := s.Context() - ri := rpcinfo.GetRPCInfo(ctx) - s.kc.opt.TracerCtl.DoFinish(ctx, ri, err) + if s.scm != nil { + s.scm.ReleaseConn(err, s.ri) + } + s.kc.opt.TracerCtl.DoFinish(s.Context(), s.ri, err) } func isRPCError(err error) bool { diff --git a/client/stream_test.go b/client/stream_test.go index c5d3117c6b..9c07647d2a 100644 --- a/client/stream_test.go +++ b/client/stream_test.go @@ -102,9 +102,9 @@ func TestStreaming(t *testing.T) { connpool := mock_remote.NewMockConnPool(ctrl) connpool.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(conn, nil) cliInfo.ConnPool = connpool - s, _ := remotecli.NewStream(ctx, mockRPCInfo, new(mocks.MockCliTransHandler), cliInfo) + s, cr, _ := remotecli.NewStream(ctx, mockRPCInfo, new(mocks.MockCliTransHandler), cliInfo) stream := newStream( - s, kc, mockRPCInfo, serviceinfo.StreamingBidirectional, + s, cr, kc, mockRPCInfo, serviceinfo.StreamingBidirectional, func(stream streaming.Stream, message interface{}) (err error) { return stream.SendMsg(message) }, @@ -211,9 +211,14 @@ func Test_newStream(t *testing.T) { TracerCtl: &rpcinfo.TraceController{}, }, } - + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scr := remotecli.NewStreamConnManager(cr) s := newStream( st, + scr, kc, ri, serviceinfo.StreamingClient, @@ -241,6 +246,9 @@ func (m *mockTracer) Finish(ctx context.Context) { } func Test_stream_Header(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + t.Run("no-error", func(t *testing.T) { headers := metadata.MD{"k": []string{"v"}} st := &mockStream{ @@ -248,8 +256,10 @@ func Test_stream_Header(t *testing.T) { return headers, nil }, } - - s := newStream(st, &kClient{}, nil, serviceinfo.StreamingBidirectional, nil, nil) + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(0) + scr := remotecli.NewStreamConnManager(cr) + s := newStream(st, scr, &kClient{}, nil, serviceinfo.StreamingBidirectional, nil, nil) md, err := s.Header() test.Assert(t, err == nil) @@ -279,8 +289,10 @@ func Test_stream_Header(t *testing.T) { TracerCtl: ctl, }, } - - s := newStream(st, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scr := remotecli.NewStreamConnManager(cr) + s := newStream(st, scr, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) md, err := s.Header() test.Assert(t, err == headerErr) @@ -290,9 +302,15 @@ func Test_stream_Header(t *testing.T) { } func Test_stream_RecvMsg(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + t.Run("no-error", func(t *testing.T) { + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(0) + scm := remotecli.NewStreamConnManager(cr) mockRPCInfo := rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation("mock_service", "mock_method"), nil, nil) - s := newStream(&mockStream{}, &kClient{}, mockRPCInfo, serviceinfo.StreamingBidirectional, nil, + s := newStream(&mockStream{}, scm, &kClient{}, mockRPCInfo, serviceinfo.StreamingBidirectional, nil, func(stream streaming.Stream, message interface{}) (err error) { return nil }, @@ -321,8 +339,11 @@ func Test_stream_RecvMsg(t *testing.T) { TracerCtl: ctl, }, } - - s := newStream(st, kc, ri, serviceinfo.StreamingClient, nil, + cr := mock_remote.NewMockConnReleaser(ctrl) + // client streaming should release connection after RecvMsg + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scm := remotecli.NewStreamConnManager(cr) + s := newStream(st, scm, kc, ri, serviceinfo.StreamingClient, nil, func(stream streaming.Stream, message interface{}) (err error) { return nil }, @@ -353,7 +374,10 @@ func Test_stream_RecvMsg(t *testing.T) { }, } - s := newStream(st, kc, ri, serviceinfo.StreamingBidirectional, nil, + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scm := remotecli.NewStreamConnManager(cr) + s := newStream(st, scm, kc, ri, serviceinfo.StreamingBidirectional, nil, func(stream streaming.Stream, message interface{}) (err error) { return recvErr }, @@ -366,8 +390,14 @@ func Test_stream_RecvMsg(t *testing.T) { } func Test_stream_SendMsg(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + t.Run("no-error", func(t *testing.T) { - s := newStream(&mockStream{}, &kClient{}, nil, serviceinfo.StreamingBidirectional, + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(0) + scm := remotecli.NewStreamConnManager(cr) + s := newStream(&mockStream{}, scm, &kClient{}, nil, serviceinfo.StreamingBidirectional, func(stream streaming.Stream, message interface{}) (err error) { return nil }, @@ -380,6 +410,9 @@ func Test_stream_SendMsg(t *testing.T) { }) t.Run("error", func(t *testing.T) { + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scm := remotecli.NewStreamConnManager(cr) sendErr := errors.New("recv error") ri := rpcinfo.NewRPCInfo(nil, nil, nil, nil, rpcinfo.NewRPCStats()) st := &mockStream{ @@ -399,7 +432,7 @@ func Test_stream_SendMsg(t *testing.T) { }, } - s := newStream(st, kc, ri, serviceinfo.StreamingBidirectional, + s := newStream(st, scm, kc, ri, serviceinfo.StreamingBidirectional, func(stream streaming.Stream, message interface{}) (err error) { return sendErr }, @@ -413,13 +446,18 @@ func Test_stream_SendMsg(t *testing.T) { } func Test_stream_Close(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(0) + scm := remotecli.NewStreamConnManager(cr) called := false s := newStream(&mockStream{ close: func() error { called = true return nil }, - }, &kClient{}, nil, serviceinfo.StreamingBidirectional, nil, nil) + }, scm, &kClient{}, nil, serviceinfo.StreamingBidirectional, nil, nil) err := s.Close() @@ -428,6 +466,9 @@ func Test_stream_Close(t *testing.T) { } func Test_stream_DoFinish(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + t.Run("no-error", func(t *testing.T) { ri := rpcinfo.NewRPCInfo(nil, nil, nil, nil, rpcinfo.NewRPCStats()) st := &mockStream{ @@ -441,7 +482,10 @@ func Test_stream_DoFinish(t *testing.T) { TracerCtl: ctl, }, } - s := newStream(st, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scm := remotecli.NewStreamConnManager(cr) + s := newStream(st, scm, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) finishCalled := false err := errors.New("any err") @@ -468,7 +512,10 @@ func Test_stream_DoFinish(t *testing.T) { TracerCtl: ctl, }, } - s := newStream(st, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scm := remotecli.NewStreamConnManager(cr) + s := newStream(st, scm, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) finishCalled := false err := errors.New("any err") @@ -495,7 +542,10 @@ func Test_stream_DoFinish(t *testing.T) { TracerCtl: ctl, }, } - s := newStream(st, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scm := remotecli.NewStreamConnManager(cr) + s := newStream(st, scm, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) finishCalled := false var err error @@ -522,7 +572,10 @@ func Test_stream_DoFinish(t *testing.T) { TracerCtl: ctl, }, } - s := newStream(st, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + scm := remotecli.NewStreamConnManager(cr) + s := newStream(st, scm, kc, ri, serviceinfo.StreamingBidirectional, nil, nil) finishCalled := false expectedErr := errors.New("error") diff --git a/internal/mocks/remote/conn_wrapper.go b/internal/mocks/remote/conn_wrapper.go new file mode 100644 index 0000000000..2e4c507890 --- /dev/null +++ b/internal/mocks/remote/conn_wrapper.go @@ -0,0 +1,64 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +// Code generated by MockGen. DO NOT EDIT. +// Source: ../../pkg/remote/remotecli/conn_wrapper.go + +// Package remote is a generated GoMock package. +package remote + +import ( + reflect "reflect" + + rpcinfo "github.com/cloudwego/kitex/pkg/rpcinfo" + gomock "github.com/golang/mock/gomock" +) + +// MockConnReleaser is a mock of ConnReleaser interface. +type MockConnReleaser struct { + ctrl *gomock.Controller + recorder *MockConnReleaserMockRecorder +} + +// MockConnReleaserMockRecorder is the mock recorder for MockConnReleaser. +type MockConnReleaserMockRecorder struct { + mock *MockConnReleaser +} + +// NewMockConnReleaser creates a new mock instance. +func NewMockConnReleaser(ctrl *gomock.Controller) *MockConnReleaser { + mock := &MockConnReleaser{ctrl: ctrl} + mock.recorder = &MockConnReleaserMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConnReleaser) EXPECT() *MockConnReleaserMockRecorder { + return m.recorder +} + +// ReleaseConn mocks base method. +func (m *MockConnReleaser) ReleaseConn(err error, ri rpcinfo.RPCInfo) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ReleaseConn", err, ri) +} + +// ReleaseConn indicates an expected call of ReleaseConn. +func (mr *MockConnReleaserMockRecorder) ReleaseConn(err, ri interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseConn", reflect.TypeOf((*MockConnReleaser)(nil).ReleaseConn), err, ri) +} diff --git a/internal/mocks/update.sh b/internal/mocks/update.sh index 2af121683f..68afa94c56 100755 --- a/internal/mocks/update.sh +++ b/internal/mocks/update.sh @@ -9,6 +9,7 @@ files=( ../../pkg/remote/bytebuf.go remote/bytebuf.go remote ../../pkg/remote/trans_handler.go remote/trans_handler.go remote ../../pkg/remote/codec.go remote/codec.go remote +../../pkg/remote/remotecli/conn_wrapper.go remote/conn_wrapper.go remote ../../pkg/remote/connpool.go remote/connpool.go remote ../../pkg/remote/dialer.go remote/dialer.go remote ../../pkg/remote/trans_meta.go remote/trans_meta.go remote diff --git a/pkg/remote/remotecli/conn_wrapper.go b/pkg/remote/remotecli/conn_wrapper.go index 925f1cc7d2..6f2da96c62 100644 --- a/pkg/remote/remotecli/conn_wrapper.go +++ b/pkg/remote/remotecli/conn_wrapper.go @@ -34,6 +34,13 @@ func init() { connWrapperPool.New = newConnWrapper } +var _ ConnReleaser = &ConnWrapper{} + +// ConnReleaser helps to release the raw connection. +type ConnReleaser interface { + ReleaseConn(err error, ri rpcinfo.RPCInfo) +} + // ConnWrapper wraps a connection. type ConnWrapper struct { connPool remote.ConnPool diff --git a/pkg/remote/remotecli/stream.go b/pkg/remote/remotecli/stream.go index 9b0fc97ff0..344a2d87b3 100644 --- a/pkg/remote/remotecli/stream.go +++ b/pkg/remote/remotecli/stream.go @@ -27,18 +27,33 @@ import ( ) // NewStream create a client side stream -func NewStream(ctx context.Context, ri rpcinfo.RPCInfo, handler remote.ClientTransHandler, opt *remote.ClientOption) (streaming.Stream, error) { +func NewStream(ctx context.Context, ri rpcinfo.RPCInfo, handler remote.ClientTransHandler, opt *remote.ClientOption) (streaming.Stream, *StreamConnManager, error) { cm := NewConnWrapper(opt.ConnPool) var err error for _, shdlr := range opt.StreamingMetaHandlers { ctx, err = shdlr.OnConnectStream(ctx) if err != nil { - return nil, err + return nil, nil, err } } rawConn, err := cm.GetConn(ctx, opt.Dialer, ri) if err != nil { - return nil, err + return nil, nil, err } - return nphttp2.NewStream(ctx, opt.SvcInfo, rawConn, handler), nil + return nphttp2.NewStream(ctx, opt.SvcInfo, rawConn, handler), &StreamConnManager{cm}, nil +} + +// NewStreamConnManager returns a new StreamConnManager +func NewStreamConnManager(cr ConnReleaser) *StreamConnManager { + return &StreamConnManager{ConnReleaser: cr} +} + +// StreamConnManager manages the underlying connection of the stream +type StreamConnManager struct { + ConnReleaser +} + +// ReleaseConn releases the raw connection of the stream +func (scm *StreamConnManager) ReleaseConn(err error, ri rpcinfo.RPCInfo) { + scm.ConnReleaser.ReleaseConn(err, ri) } diff --git a/pkg/remote/remotecli/stream_test.go b/pkg/remote/remotecli/stream_test.go index abd7c5394a..196a14dc89 100644 --- a/pkg/remote/remotecli/stream_test.go +++ b/pkg/remote/remotecli/stream_test.go @@ -23,6 +23,7 @@ import ( "github.com/golang/mock/gomock" "github.com/cloudwego/kitex/internal/mocks" + mock_remote "github.com/cloudwego/kitex/internal/mocks/remote" "github.com/cloudwego/kitex/internal/test" "github.com/cloudwego/kitex/pkg/remote" "github.com/cloudwego/kitex/pkg/rpcinfo" @@ -47,6 +48,18 @@ func TestNewStream(t *testing.T) { }, } - _, err := NewStream(ctx, ri, hdlr, opt) + _, _, err := NewStream(ctx, ri, hdlr, opt) test.Assert(t, err == nil, err) } + +func TestStreamConnManagerReleaseConn(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cr := mock_remote.NewMockConnReleaser(ctrl) + cr.EXPECT().ReleaseConn(gomock.Any(), gomock.Any()).Times(1) + + scr := NewStreamConnManager(cr) + test.Assert(t, scr != nil) + test.Assert(t, scr.ConnReleaser == cr) + scr.ReleaseConn(nil, nil) +} diff --git a/pkg/remote/trans/nphttp2/conn_pool.go b/pkg/remote/trans/nphttp2/conn_pool.go index ce50a8f7a3..cdd1a2f785 100644 --- a/pkg/remote/trans/nphttp2/conn_pool.go +++ b/pkg/remote/trans/nphttp2/conn_pool.go @@ -206,6 +206,9 @@ func (p *connPool) createShortConn(ctx context.Context, network, address string, // Discard implements the ConnPool interface. func (p *connPool) Discard(conn net.Conn) error { + if p.connOpts.ShortConn { + return p.release(conn) + } return nil } diff --git a/pkg/remote/trans/nphttp2/conn_pool_test.go b/pkg/remote/trans/nphttp2/conn_pool_test.go index f67981215e..21d20867a7 100644 --- a/pkg/remote/trans/nphttp2/conn_pool_test.go +++ b/pkg/remote/trans/nphttp2/conn_pool_test.go @@ -17,10 +17,13 @@ package nphttp2 import ( + "net" + "sync/atomic" "testing" "time" "github.com/cloudwego/kitex/internal/test" + "github.com/cloudwego/kitex/pkg/remote" ) func TestConnPool(t *testing.T) { @@ -52,3 +55,53 @@ func TestConnPool(t *testing.T) { _, ok := connPool.conns.Load(mockAddr0) test.Assert(t, !ok) } + +func TestReleaseConn(t *testing.T) { + // short connection + // the connection will be released when put back to the pool + var closed1 int32 = 0 + dialFunc1 := func(network, address string, timeout time.Duration) (net.Conn, error) { + npConn := newMockNpConn(address) + npConn.CloseFunc = func() error { + atomic.StoreInt32(&closed1, 1) + return nil + } + npConn.mockSettingFrame() + return npConn, nil + } + shortCP := newMockConnPool() + shortCP.connOpts.ShortConn = true + defer shortCP.Close() + + conn, err := shortCP.Get(newMockCtxWithRPCInfo(), "tcp", mockAddr0, remote.ConnOption{Dialer: newMockDialerWithDialFunc(dialFunc1)}) + // close stream to ensure no active stream on this connection, + // which will be released when put back to the connection pool and closed by GracefulClose + s := conn.(*clientConn).s + conn.(*clientConn).tr.CloseStream(s, nil) + test.Assert(t, err == nil, err) + time.Sleep(100 * time.Millisecond) + shortCP.Put(conn) + test.Assert(t, atomic.LoadInt32(&closed1) == 1) + + // long connection + // the connection will not be released when put back to the pool + var closed2 int32 = 0 + dialFunc2 := func(network, address string, timeout time.Duration) (net.Conn, error) { + npConn := newMockNpConn(address) + npConn.CloseFunc = func() error { + atomic.StoreInt32(&closed2, 1) + return nil + } + npConn.mockSettingFrame() + return npConn, nil + } + longCP := newMockConnPool() + longCP.connOpts.ShortConn = false + defer longCP.Close() + + longConn, err := longCP.Get(newMockCtxWithRPCInfo(), "tcp", mockAddr0, remote.ConnOption{Dialer: newMockDialerWithDialFunc(dialFunc2)}) + longConn.Close() + test.Assert(t, err == nil, err) + longCP.Put(longConn) + test.Assert(t, atomic.LoadInt32(&closed2) == 0) +} diff --git a/pkg/remote/trans/nphttp2/mocks_test.go b/pkg/remote/trans/nphttp2/mocks_test.go index f0aeaf3bf2..e027235869 100644 --- a/pkg/remote/trans/nphttp2/mocks_test.go +++ b/pkg/remote/trans/nphttp2/mocks_test.go @@ -354,6 +354,12 @@ func newMockDialer() *remote.SynthesizedDialer { return d } +func newMockDialerWithDialFunc(dialFunc func(network, address string, timeout time.Duration) (net.Conn, error)) *remote.SynthesizedDialer { + d := &remote.SynthesizedDialer{} + d.DialFunc = dialFunc + return d +} + func newMockCtxWithRPCInfo() context.Context { return rpcinfo.NewCtxWithRPCInfo(context.Background(), newMockRPCInfo()) } From ada7c08d1e4597e0e5bdfe17110e491d90400121 Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Fri, 19 Apr 2024 15:40:55 +0800 Subject: [PATCH 17/49] chore: update dynamicgo to v0.2.3 (#1334) --- go.mod | 2 +- go.sum | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 1e307876ba..ce27c3cf0f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/bytedance/sonic v1.11.5 github.com/choleraehyq/pid v0.0.18 github.com/cloudwego/configmanager v0.2.1 - github.com/cloudwego/dynamicgo v0.2.2 + github.com/cloudwego/dynamicgo v0.2.3 github.com/cloudwego/fastpb v0.0.4 github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 diff --git a/go.sum b/go.sum index 160a74075e..815be21baf 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,9 @@ github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1um github.com/cloudwego/configmanager v0.2.1 h1:JRmM9HPl8IafjrUU5waNboYjhiF3H7HY5oKuyoiiZto= github.com/cloudwego/configmanager v0.2.1/go.mod h1:0oD/BWaTuznBOawVeTmTl3LE99RWaw7rX2jECowmY58= github.com/cloudwego/dynamicgo v0.2.2-dep3/go.mod h1:oFLzd9SEUtU7XbSc7AT9e5xoAV1OJ1mVpudtUOiD7PQ= -github.com/cloudwego/dynamicgo v0.2.2 h1:jhh8Zo0VkUsI3IrdAHvuTrmslDhZ8tH+3WqYZcgykt8= github.com/cloudwego/dynamicgo v0.2.2/go.mod h1:k840iCFH9ng9PBqr6jIoOyZxdk58EPEccrbfOk4ni1s= +github.com/cloudwego/dynamicgo v0.2.3 h1:y+W/ISuVW8uDFZ94NGOMMPB1SWaRJ0o+8qKx/D1kMic= +github.com/cloudwego/dynamicgo v0.2.3/go.mod h1:fvksgIX2piXJ6cmLxmAfOYDf06lZh8Nfn06ulqJPDsw= github.com/cloudwego/fastpb v0.0.4 h1:/ROVVfoFtpfc+1pkQLzGs+azjxUbSOsAqSY4tAAx4mg= github.com/cloudwego/fastpb v0.0.4/go.mod h1:/V13XFTq2TUkxj2qWReV8MwfPC4NnPcy6FsrojnsSG0= github.com/cloudwego/frugal v0.1.15 h1:LC55UJKhQPMFVjDPbE+LJcF7etZjSx6uokG1tk0wPK0= @@ -43,6 +44,7 @@ github.com/cloudwego/iasm v0.0.9/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/kitex v0.9.3-dep3/go.mod h1:87pfgsZMaso0tFISQZzMhwmh6pajfVmw/R3COsh2pCg= +github.com/cloudwego/kitex v0.9.3-rc/go.mod h1:QurmwA8Wh/s7qz6C+Da9sc9B4TRW6q4TN6Y56mu90SE= github.com/cloudwego/localsession v0.0.2 h1:N9/IDtCPj1fCL9bCTP+DbXx3f40YjVYWcwkJG0YhQkY= github.com/cloudwego/localsession v0.0.2/go.mod h1:kiJxmvAcy4PLgKtEnPS5AXed3xCiXcs7Z+KBHP72Wv8= github.com/cloudwego/netpoll v0.6.0 h1:JRMkrA1o8k/4quxzg6Q1XM+zIhwZsyoWlq6ef+ht31U= From 7d0634b7d8f53625cb74908a2dac0519cbd5beb9 Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Fri, 19 Apr 2024 16:59:38 +0800 Subject: [PATCH 18/49] chore fix --- tool/internal_pkg/tpl/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/internal_pkg/tpl/service.go b/tool/internal_pkg/tpl/service.go index d426a91471..d5a72428dc 100644 --- a/tool/internal_pkg/tpl/service.go +++ b/tool/internal_pkg/tpl/service.go @@ -73,12 +73,12 @@ func serviceInfo() *kitex.ServiceInfo { return {{LowerFirst .ServiceName}}ServiceInfo } -// for client +// for stream client func serviceInfoForStreamClient() *kitex.ServiceInfo { return {{LowerFirst .ServiceName}}ServiceInfoForStreamClient } -// for stream client +// for client func serviceInfoForClient() *kitex.ServiceInfo { return {{LowerFirst .ServiceName}}ServiceInfoForClient } From 45c7f72b28dfdea90d4886f70d68b348a2a9a3b9 Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Fri, 19 Apr 2024 17:41:23 +0800 Subject: [PATCH 19/49] change SkipListenAddr pos --- pkg/registry/registry.go | 5 +++-- server/server_test.go | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 637e3f0ad6..c8067aad44 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -37,8 +37,6 @@ type Info struct { ServiceName string // Addr will be set in kitex by default Addr net.Addr - // SkipListenAddr is used to prevent the listen addr from overriding the Addr - SkipListenAddr bool // PayloadCodec will be set in kitex by default, like thrift, protobuf PayloadCodec string @@ -48,6 +46,9 @@ type Info struct { // extend other infos with Tags. Tags map[string]string + + // SkipListenAddr is used to prevent the listen addr from overriding the Addr + SkipListenAddr bool } // NoopRegistry is an empty implement of Registry diff --git a/server/server_test.go b/server/server_test.go index d5500faba3..21b4516da4 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -481,7 +481,6 @@ func TestServiceRegistryInfoWithSkipListenAddr(t *testing.T) { func TestServiceRegistryInfoWithoutSkipListenAddr(t *testing.T) { realAddr := utils.NewNetAddr("tcp", test.GetLocalAddress()) - println(realAddr) registryInfo := ®istry.Info{ Weight: 100, Addr: realAddr, From a3b9eb4a6d0b801dfc84fc8965c54b854c64f4e6 Mon Sep 17 00:00:00 2001 From: Joway Date: Tue, 23 Apr 2024 17:45:18 +0800 Subject: [PATCH 20/49] perf(codec): support fast write nocopy when using netpoll link buffer (#1322) --- pkg/protocol/bthrift/binary.go | 6 + pkg/protocol/bthrift/interface.go | 1 - pkg/remote/bytebuf.go | 4 +- pkg/remote/codec/default_codec_test.go | 434 ++++++++++-------- pkg/remote/codec/header_codec_test.go | 305 ++++++------ pkg/remote/codec/thrift/thrift.go | 12 +- pkg/remote/codec/thrift/thrift_frugal.go | 8 +- pkg/remote/codec/thrift/thrift_frugal_test.go | 113 ++--- pkg/remote/codec/thrift/thrift_test.go | 233 ++++++---- pkg/remote/trans/netpoll/bytebuf.go | 11 + 10 files changed, 625 insertions(+), 502 deletions(-) diff --git a/pkg/protocol/bthrift/binary.go b/pkg/protocol/bthrift/binary.go index fa0f6c4ae6..b1b4024530 100644 --- a/pkg/protocol/bthrift/binary.go +++ b/pkg/protocol/bthrift/binary.go @@ -37,6 +37,8 @@ var ( spanCache = mem.NewSpanCache(1024 * 1024) // 1MB ) +const binaryInplaceThreshold = 4096 // 4k + type binaryProtocol struct{} func (binaryProtocol) WriteMessageBegin(buf []byte, name string, typeID thrift.TMessageType, seqid int32) int { @@ -149,6 +151,10 @@ func (binaryProtocol) WriteStringNocopy(buf []byte, binaryWriter BinaryWriter, v func (binaryProtocol) WriteBinaryNocopy(buf []byte, binaryWriter BinaryWriter, value []byte) int { l := Binary.WriteI32(buf, int32(len(value))) + if binaryWriter != nil && len(value) > binaryInplaceThreshold { + binaryWriter.WriteDirect(value, len(buf[l:])) + return l + } copy(buf[l:], value) return l + len(value) } diff --git a/pkg/protocol/bthrift/interface.go b/pkg/protocol/bthrift/interface.go index 5369270217..c5c64b152b 100644 --- a/pkg/protocol/bthrift/interface.go +++ b/pkg/protocol/bthrift/interface.go @@ -22,7 +22,6 @@ import ( ) // BinaryWriter . -// Deprecated: removed from FastCodec in order to be compatible with Bytebuffer other than netpoll. type BinaryWriter interface { WriteDirect(b []byte, remainCap int) error } diff --git a/pkg/remote/bytebuf.go b/pkg/remote/bytebuf.go index 9f1dbe4645..bbd5dbbd29 100644 --- a/pkg/remote/bytebuf.go +++ b/pkg/remote/bytebuf.go @@ -29,9 +29,11 @@ type ByteBufferFactory interface { // NocopyWrite is to write []byte without copying, and splits the original buffer. // It is used with linked buffer implement. type NocopyWrite interface { - // the buf will be wrapped as a new data node no copy, then insert into the linked buffer. + // WriteDirect will wrap buf as a new data node no copy, then insert into the linked buffer. // remainCap is the remain capacity of origin buff. WriteDirect(buf []byte, remainCap int) error + // MallocAck correct the real malloc len to n + MallocAck(n int) error } // FrameWrite is to write header and data buffer separately to avoid memory copy diff --git a/pkg/remote/codec/default_codec_test.go b/pkg/remote/codec/default_codec_test.go index e27ecfca29..a1a2509671 100644 --- a/pkg/remote/codec/default_codec_test.go +++ b/pkg/remote/codec/default_codec_test.go @@ -23,231 +23,277 @@ import ( "testing" "github.com/bytedance/mockey" + "github.com/cloudwego/netpoll" "github.com/golang/mock/gomock" "github.com/cloudwego/kitex/internal/mocks" mocksremote "github.com/cloudwego/kitex/internal/mocks/remote" "github.com/cloudwego/kitex/internal/test" "github.com/cloudwego/kitex/pkg/remote" + netpolltrans "github.com/cloudwego/kitex/pkg/remote/trans/netpoll" "github.com/cloudwego/kitex/pkg/rpcinfo" "github.com/cloudwego/kitex/pkg/serviceinfo" "github.com/cloudwego/kitex/transport" ) -func TestThriftProtocolCheck(t *testing.T) { - var req interface{} - var rbf remote.ByteBuffer - var ttheader bool - var flagBuf []byte - var ri rpcinfo.RPCInfo - var msg remote.Message - - resetRIAndMSG := func() { - ri = rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation("", ""), rpcinfo.NewRPCConfig(), rpcinfo.NewRPCStats()) - msg = remote.NewMessage(req, mocks.ServiceInfo(), ri, remote.Call, remote.Server) - } +var transportBuffers = []struct { + Name string + NewBuffer func() remote.ByteBuffer +}{ + { + Name: "BytesBuffer", + NewBuffer: func() remote.ByteBuffer { + return remote.NewReaderWriterBuffer(1024) + }, + }, + { + Name: "NetpollBuffer", + NewBuffer: func() remote.ByteBuffer { + return netpolltrans.NewReaderWriterByteBuffer(netpoll.NewLinkBuffer(1024)) + }, + }, +} - // 1. ttheader - resetRIAndMSG() - flagBuf = make([]byte, 8*2) - binary.BigEndian.PutUint32(flagBuf, uint32(10)) - binary.BigEndian.PutUint32(flagBuf[4:8], TTHeaderMagic) - binary.BigEndian.PutUint32(flagBuf[8:12], ThriftV1Magic) - ttheader = IsTTHeader(flagBuf) - test.Assert(t, ttheader) - if ttheader { - flagBuf = flagBuf[8:] - } - rbf = remote.NewReaderBuffer(flagBuf) - err := checkPayload(flagBuf, msg, rbf, ttheader, 10) - test.Assert(t, err == nil, err) - test.Assert(t, msg.ProtocolInfo().TransProto == transport.TTHeader) - test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.TTHeader == transport.TTHeader) - test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) - - // 2. ttheader framed - resetRIAndMSG() - flagBuf = make([]byte, 8*2) - binary.BigEndian.PutUint32(flagBuf, uint32(10)) - binary.BigEndian.PutUint32(flagBuf[4:8], TTHeaderMagic) - binary.BigEndian.PutUint32(flagBuf[12:], ThriftV1Magic) - ttheader = IsTTHeader(flagBuf) - test.Assert(t, ttheader) - if ttheader { - flagBuf = flagBuf[8:] +func TestThriftProtocolCheck(t *testing.T) { + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + var req interface{} + var rbf remote.ByteBuffer + var ttheader bool + var flagBuf []byte + var ri rpcinfo.RPCInfo + var msg remote.Message + + resetRIAndMSG := func() { + ri = rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation("", ""), rpcinfo.NewRPCConfig(), rpcinfo.NewRPCStats()) + msg = remote.NewMessage(req, mocks.ServiceInfo(), ri, remote.Call, remote.Server) + } + + // 1. ttheader + resetRIAndMSG() + flagBuf = make([]byte, 8*2) + binary.BigEndian.PutUint32(flagBuf, uint32(10)) + binary.BigEndian.PutUint32(flagBuf[4:8], TTHeaderMagic) + binary.BigEndian.PutUint32(flagBuf[8:12], ThriftV1Magic) + ttheader = IsTTHeader(flagBuf) + test.Assert(t, ttheader) + if ttheader { + flagBuf = flagBuf[8:] + } + rbf = tb.NewBuffer() + rbf.WriteBinary(flagBuf) + rbf.Flush() + err := checkPayload(flagBuf, msg, rbf, ttheader, 10) + test.Assert(t, err == nil, err) + test.Assert(t, msg.ProtocolInfo().TransProto == transport.TTHeader) + test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.TTHeader == transport.TTHeader) + test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) + + // 2. ttheader framed + resetRIAndMSG() + flagBuf = make([]byte, 8*2) + binary.BigEndian.PutUint32(flagBuf, uint32(10)) + binary.BigEndian.PutUint32(flagBuf[4:8], TTHeaderMagic) + binary.BigEndian.PutUint32(flagBuf[12:], ThriftV1Magic) + ttheader = IsTTHeader(flagBuf) + test.Assert(t, ttheader) + if ttheader { + flagBuf = flagBuf[8:] + } + rbf = tb.NewBuffer() + rbf.WriteBinary(flagBuf) + rbf.Flush() + err = checkPayload(flagBuf, msg, rbf, ttheader, 10) + test.Assert(t, err == nil, err) + test.Assert(t, msg.ProtocolInfo().TransProto == transport.TTHeaderFramed) + test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.TTHeaderFramed == transport.TTHeaderFramed) + test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) + + // 3. thrift framed + resetRIAndMSG() + flagBuf = make([]byte, 8*2) + binary.BigEndian.PutUint32(flagBuf, uint32(10)) + binary.BigEndian.PutUint32(flagBuf[4:8], ThriftV1Magic) + ttheader = IsTTHeader(flagBuf) + test.Assert(t, !ttheader) + rbf = tb.NewBuffer() + rbf.WriteBinary(flagBuf) + rbf.Flush() + err = checkPayload(flagBuf, msg, rbf, ttheader, 10) + test.Assert(t, err == nil, err) + err = checkPayload(flagBuf, msg, rbf, ttheader, 9) + test.Assert(t, err != nil, err) + test.Assert(t, msg.ProtocolInfo().TransProto == transport.Framed) + test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.Framed == transport.Framed) + test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) + + // 4. thrift pure payload + // resetRIAndMSG() // the logic below needs to check payload length set by the front case, so we don't reset ri + flagBuf = make([]byte, 8*2) + binary.BigEndian.PutUint32(flagBuf, uint32(10)) + binary.BigEndian.PutUint32(flagBuf[0:4], ThriftV1Magic) + ttheader = IsTTHeader(flagBuf) + test.Assert(t, !ttheader) + rbf = tb.NewBuffer() + rbf.WriteBinary(flagBuf) + rbf.Flush() + err = checkPayload(flagBuf, msg, rbf, ttheader, 10) + test.Assert(t, err == nil, err) + err = checkPayload(flagBuf, msg, rbf, ttheader, 9) + test.Assert(t, err != nil, err) + test.Assert(t, msg.ProtocolInfo().TransProto == transport.PurePayload) + test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.PurePayload == transport.PurePayload) + test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) + }) } - rbf = remote.NewReaderBuffer(flagBuf) - err = checkPayload(flagBuf, msg, rbf, ttheader, 10) - test.Assert(t, err == nil, err) - test.Assert(t, msg.ProtocolInfo().TransProto == transport.TTHeaderFramed) - test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.TTHeaderFramed == transport.TTHeaderFramed) - test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) - - // 3. thrift framed - resetRIAndMSG() - flagBuf = make([]byte, 8*2) - binary.BigEndian.PutUint32(flagBuf, uint32(10)) - binary.BigEndian.PutUint32(flagBuf[4:8], ThriftV1Magic) - ttheader = IsTTHeader(flagBuf) - test.Assert(t, !ttheader) - rbf = remote.NewReaderBuffer(flagBuf) - err = checkPayload(flagBuf, msg, rbf, ttheader, 10) - test.Assert(t, err == nil, err) - err = checkPayload(flagBuf, msg, rbf, ttheader, 9) - test.Assert(t, err != nil, err) - test.Assert(t, msg.ProtocolInfo().TransProto == transport.Framed) - test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.Framed == transport.Framed) - test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) - - // 4. thrift pure payload - // resetRIAndMSG() // the logic below needs to check payload length set by the front case, so we don't reset ri - flagBuf = make([]byte, 8*2) - binary.BigEndian.PutUint32(flagBuf, uint32(10)) - binary.BigEndian.PutUint32(flagBuf[0:4], ThriftV1Magic) - ttheader = IsTTHeader(flagBuf) - test.Assert(t, !ttheader) - rbf = remote.NewReaderBuffer(flagBuf) - err = checkPayload(flagBuf, msg, rbf, ttheader, 10) - test.Assert(t, err == nil, err) - err = checkPayload(flagBuf, msg, rbf, ttheader, 9) - test.Assert(t, err != nil, err) - test.Assert(t, msg.ProtocolInfo().TransProto == transport.PurePayload) - test.Assert(t, msg.RPCInfo().Config().TransportProtocol()&transport.PurePayload == transport.PurePayload) - test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Thrift) } func TestProtobufProtocolCheck(t *testing.T) { - var req interface{} - var rbf remote.ByteBuffer - var ttheader bool - var flagBuf []byte - ri := rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation("", ""), rpcinfo.NewRPCConfig(), rpcinfo.NewRPCStats()) - msg := remote.NewMessage(req, mocks.ServiceInfo(), ri, remote.Call, remote.Server) - - // 1. ttheader framed - flagBuf = make([]byte, 8*2) - binary.BigEndian.PutUint32(flagBuf, uint32(10)) - binary.BigEndian.PutUint32(flagBuf[4:8], TTHeaderMagic) - binary.BigEndian.PutUint32(flagBuf[12:], ProtobufV1Magic) - ttheader = IsTTHeader(flagBuf) - test.Assert(t, ttheader) - if ttheader { - flagBuf = flagBuf[8:] + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + var req interface{} + var rbf remote.ByteBuffer + var ttheader bool + var flagBuf []byte + ri := rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation("", ""), rpcinfo.NewRPCConfig(), rpcinfo.NewRPCStats()) + msg := remote.NewMessage(req, mocks.ServiceInfo(), ri, remote.Call, remote.Server) + + // 1. ttheader framed + flagBuf = make([]byte, 8*2) + binary.BigEndian.PutUint32(flagBuf, uint32(10)) + binary.BigEndian.PutUint32(flagBuf[4:8], TTHeaderMagic) + binary.BigEndian.PutUint32(flagBuf[12:], ProtobufV1Magic) + ttheader = IsTTHeader(flagBuf) + test.Assert(t, ttheader) + if ttheader { + flagBuf = flagBuf[8:] + } + rbf = tb.NewBuffer() + rbf.WriteBinary(flagBuf) + rbf.Flush() + err := checkPayload(flagBuf, msg, rbf, ttheader, 10) + test.Assert(t, err == nil, err) + test.Assert(t, msg.ProtocolInfo().TransProto == transport.TTHeaderFramed) + test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Protobuf) + + // 2. protobuf framed + flagBuf = make([]byte, 8*2) + binary.BigEndian.PutUint32(flagBuf, uint32(10)) + binary.BigEndian.PutUint32(flagBuf[4:8], ProtobufV1Magic) + ttheader = IsTTHeader(flagBuf) + test.Assert(t, !ttheader) + rbf = tb.NewBuffer() + rbf.WriteBinary(flagBuf) + rbf.Flush() + err = checkPayload(flagBuf, msg, rbf, ttheader, 10) + test.Assert(t, err == nil, err) + err = checkPayload(flagBuf, msg, rbf, ttheader, 9) + test.Assert(t, err != nil, err) + test.Assert(t, msg.ProtocolInfo().TransProto == transport.Framed) + test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Protobuf) + }) } - rbf = remote.NewReaderBuffer(flagBuf) - err := checkPayload(flagBuf, msg, rbf, ttheader, 10) - test.Assert(t, err == nil, err) - test.Assert(t, msg.ProtocolInfo().TransProto == transport.TTHeaderFramed) - test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Protobuf) - - // 2. protobuf framed - flagBuf = make([]byte, 8*2) - binary.BigEndian.PutUint32(flagBuf, uint32(10)) - binary.BigEndian.PutUint32(flagBuf[4:8], ProtobufV1Magic) - ttheader = IsTTHeader(flagBuf) - test.Assert(t, !ttheader) - rbf = remote.NewReaderBuffer(flagBuf) - err = checkPayload(flagBuf, msg, rbf, ttheader, 10) - test.Assert(t, err == nil, err) - err = checkPayload(flagBuf, msg, rbf, ttheader, 9) - test.Assert(t, err != nil, err) - test.Assert(t, msg.ProtocolInfo().TransProto == transport.Framed) - test.Assert(t, msg.ProtocolInfo().CodecType == serviceinfo.Protobuf) } func TestDefaultCodec_Encode_Decode(t *testing.T) { remote.PutPayloadCode(serviceinfo.Thrift, mpc) - dc := NewDefaultCodec() - ctx := context.Background() - intKVInfo := prepareIntKVInfo() - strKVInfo := prepareStrKVInfo() - sendMsg := initClientSendMsg(transport.TTHeader) - sendMsg.TransInfo().PutTransIntInfo(intKVInfo) - sendMsg.TransInfo().PutTransStrInfo(strKVInfo) - - // test encode err - out := remote.NewReaderBuffer([]byte{}) - err := dc.Encode(ctx, sendMsg, out) - test.Assert(t, err != nil) - - // encode - out = remote.NewWriterBuffer(256) - err = dc.Encode(ctx, sendMsg, out) - test.Assert(t, err == nil, err) - - // decode - recvMsg := initServerRecvMsg() - buf, err := out.Bytes() - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = dc.Decode(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - - intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() - strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() - test.DeepEqual(t, intKVInfoRecv, intKVInfo) - test.DeepEqual(t, strKVInfoRecv, strKVInfo) - test.Assert(t, sendMsg.RPCInfo().Invocation().SeqID() == recvMsg.RPCInfo().Invocation().SeqID()) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + dc := NewDefaultCodec() + ctx := context.Background() + intKVInfo := prepareIntKVInfo() + strKVInfo := prepareStrKVInfo() + sendMsg := initClientSendMsg(transport.TTHeader) + sendMsg.TransInfo().PutTransIntInfo(intKVInfo) + sendMsg.TransInfo().PutTransStrInfo(strKVInfo) + + // encode + buf := tb.NewBuffer() + err := dc.Encode(ctx, sendMsg, buf) + test.Assert(t, err == nil, err) + buf.Flush() + + // decode + recvMsg := initServerRecvMsg() + test.Assert(t, err == nil, err) + err = dc.Decode(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) + + intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() + strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() + test.DeepEqual(t, intKVInfoRecv, intKVInfo) + test.DeepEqual(t, strKVInfoRecv, strKVInfo) + test.Assert(t, sendMsg.RPCInfo().Invocation().SeqID() == recvMsg.RPCInfo().Invocation().SeqID()) + }) + } } func TestDefaultSizedCodec_Encode_Decode(t *testing.T) { remote.PutPayloadCode(serviceinfo.Thrift, mpc) - smallDc := NewDefaultCodecWithSizeLimit(1) - largeDc := NewDefaultCodecWithSizeLimit(1024) - ctx := context.Background() - intKVInfo := prepareIntKVInfo() - strKVInfo := prepareStrKVInfo() - sendMsg := initClientSendMsg(transport.TTHeader) - sendMsg.TransInfo().PutTransIntInfo(intKVInfo) - sendMsg.TransInfo().PutTransStrInfo(strKVInfo) - - // encode - smallOut := remote.NewWriterBuffer(256) - largeOut := remote.NewWriterBuffer(256) - err := smallDc.Encode(ctx, sendMsg, smallOut) - test.Assert(t, err != nil, err) - err = largeDc.Encode(ctx, sendMsg, largeOut) - test.Assert(t, err == nil, err) - - // decode - recvMsg := initServerRecvMsg() - smallBuf, _ := smallOut.Bytes() - largeBuf, _ := largeOut.Bytes() - err = smallDc.Decode(ctx, recvMsg, remote.NewReaderBuffer(smallBuf)) - test.Assert(t, err != nil, err) - err = largeDc.Decode(ctx, recvMsg, remote.NewReaderBuffer(largeBuf)) - test.Assert(t, err == nil, err) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + smallDc := NewDefaultCodecWithSizeLimit(1) + largeDc := NewDefaultCodecWithSizeLimit(1024) + ctx := context.Background() + intKVInfo := prepareIntKVInfo() + strKVInfo := prepareStrKVInfo() + sendMsg := initClientSendMsg(transport.TTHeader) + sendMsg.TransInfo().PutTransIntInfo(intKVInfo) + sendMsg.TransInfo().PutTransStrInfo(strKVInfo) + + // encode + smallBuf := tb.NewBuffer() + largeBuf := tb.NewBuffer() + err := smallDc.Encode(ctx, sendMsg, smallBuf) + test.Assert(t, err != nil, err) + err = largeDc.Encode(ctx, sendMsg, largeBuf) + test.Assert(t, err == nil, err) + smallBuf.Flush() + largeBuf.Flush() + + // decode + recvMsg := initServerRecvMsg() + err = smallDc.Decode(ctx, recvMsg, smallBuf) + test.Assert(t, err != nil, err) + err = largeDc.Decode(ctx, recvMsg, largeBuf) + test.Assert(t, err == nil, err) + }) + } } func TestCodecTypeNotMatchWithServiceInfoPayloadCodec(t *testing.T) { - var req interface{} - remote.PutPayloadCode(serviceinfo.Thrift, mpc) - remote.PutPayloadCode(serviceinfo.Protobuf, mpc) - ri := rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation("", ""), rpcinfo.NewRPCConfig(), rpcinfo.NewRPCStats()) - codec := NewDefaultCodec() - - // case 1: the payloadCodec of svcInfo is Protobuf, CodecType of message is Thrift - svcInfo := &serviceinfo.ServiceInfo{ - PayloadCodec: serviceinfo.Protobuf, - } - msg := remote.NewMessage(req, svcInfo, ri, remote.Call, remote.Server) - msg.SetProtocolInfo(remote.ProtocolInfo{TransProto: transport.TTHeader, CodecType: serviceinfo.Thrift}) - err := codec.Encode(context.Background(), msg, remote.NewWriterBuffer(256)) - test.Assert(t, err == nil, err) - - // case 2: the payloadCodec of svcInfo is Thrift, CodecType of message is Protobuf - svcInfo = &serviceinfo.ServiceInfo{ - PayloadCodec: serviceinfo.Thrift, + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + var req interface{} + remote.PutPayloadCode(serviceinfo.Thrift, mpc) + remote.PutPayloadCode(serviceinfo.Protobuf, mpc) + ri := rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation("", ""), rpcinfo.NewRPCConfig(), rpcinfo.NewRPCStats()) + codec := NewDefaultCodec() + + // case 1: the payloadCodec of svcInfo is Protobuf, CodecType of message is Thrift + svcInfo := &serviceinfo.ServiceInfo{ + PayloadCodec: serviceinfo.Protobuf, + } + msg := remote.NewMessage(req, svcInfo, ri, remote.Call, remote.Server) + msg.SetProtocolInfo(remote.ProtocolInfo{TransProto: transport.TTHeader, CodecType: serviceinfo.Thrift}) + err := codec.Encode(context.Background(), msg, tb.NewBuffer()) + test.Assert(t, err == nil, err) + + // case 2: the payloadCodec of svcInfo is Thrift, CodecType of message is Protobuf + svcInfo = &serviceinfo.ServiceInfo{ + PayloadCodec: serviceinfo.Thrift, + } + msg = remote.NewMessage(req, svcInfo, ri, remote.Call, remote.Server) + msg.SetProtocolInfo(remote.ProtocolInfo{TransProto: transport.TTHeader, CodecType: serviceinfo.Protobuf}) + err = codec.Encode(context.Background(), msg, tb.NewBuffer()) + test.Assert(t, err != nil) + msg.SetProtocolInfo(remote.ProtocolInfo{TransProto: transport.Framed, CodecType: serviceinfo.Protobuf}) + err = codec.Encode(context.Background(), msg, tb.NewBuffer()) + test.Assert(t, err == nil) + }) } - msg = remote.NewMessage(req, svcInfo, ri, remote.Call, remote.Server) - msg.SetProtocolInfo(remote.ProtocolInfo{TransProto: transport.TTHeader, CodecType: serviceinfo.Protobuf}) - err = codec.Encode(context.Background(), msg, remote.NewWriterBuffer(256)) - test.Assert(t, err != nil) - msg.SetProtocolInfo(remote.ProtocolInfo{TransProto: transport.Framed, CodecType: serviceinfo.Protobuf}) - err = codec.Encode(context.Background(), msg, remote.NewWriterBuffer(256)) - test.Assert(t, err == nil) } var mpc remote.PayloadCodec = mockPayloadCodec{} diff --git a/pkg/remote/codec/header_codec_test.go b/pkg/remote/codec/header_codec_test.go index f23c936669..0c8066955e 100644 --- a/pkg/remote/codec/header_codec_test.go +++ b/pkg/remote/codec/header_codec_test.go @@ -40,167 +40,174 @@ import ( var mockPayloadLen = 100 func TestTTHeaderCodec(t *testing.T) { - ctx := context.Background() - sendMsg := initClientSendMsg(transport.TTHeader) - - // encode - out := remote.NewWriterBuffer(256) - totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, out) - binary.BigEndian.PutUint32(totalLenField, uint32(out.MallocLen()-Size32+mockPayloadLen)) - test.Assert(t, err == nil, err) - - // decode - recvMsg := initServerRecvMsg() - buf, err := out.Bytes() - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = ttHeaderCodec.decode(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + ctx := context.Background() + sendMsg := initClientSendMsg(transport.TTHeader) + + // encode + buf := tb.NewBuffer() + totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, buf) + binary.BigEndian.PutUint32(totalLenField, uint32(buf.MallocLen()-Size32+mockPayloadLen)) + test.Assert(t, err == nil, err) + buf.Flush() + + // decode + recvMsg := initServerRecvMsg() + test.Assert(t, err == nil, err) + err = ttHeaderCodec.decode(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) + test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) + }) + } } func TestTTHeaderCodecWithTransInfo(t *testing.T) { - ctx := context.Background() - intKVInfo := prepareIntKVInfo() - strKVInfo := prepareStrKVInfo() - sendMsg := initClientSendMsg(transport.TTHeader) - sendMsg.TransInfo().PutTransIntInfo(intKVInfo) - sendMsg.TransInfo().PutTransStrInfo(strKVInfo) - sendMsg.Tags()[HeaderFlagsKey] = HeaderFlagSupportOutOfOrder - - // encode - out := remote.NewWriterBuffer(256) - totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, out) - binary.BigEndian.PutUint32(totalLenField, uint32(out.MallocLen()-Size32+mockPayloadLen)) - test.Assert(t, err == nil, err) - - // decode - recvMsg := initServerRecvMsg() - buf, err := out.Bytes() - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = ttHeaderCodec.decode(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) - - intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() - strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() - test.DeepEqual(t, intKVInfoRecv, intKVInfo) - test.DeepEqual(t, strKVInfoRecv, strKVInfo) - flag := recvMsg.Tags()[HeaderFlagsKey] - test.Assert(t, flag != nil) - test.Assert(t, flag == HeaderFlagSupportOutOfOrder) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + ctx := context.Background() + intKVInfo := prepareIntKVInfo() + strKVInfo := prepareStrKVInfo() + sendMsg := initClientSendMsg(transport.TTHeader) + sendMsg.TransInfo().PutTransIntInfo(intKVInfo) + sendMsg.TransInfo().PutTransStrInfo(strKVInfo) + sendMsg.Tags()[HeaderFlagsKey] = HeaderFlagSupportOutOfOrder + + // encode + buf := tb.NewBuffer() + totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, buf) + binary.BigEndian.PutUint32(totalLenField, uint32(buf.MallocLen()-Size32+mockPayloadLen)) + test.Assert(t, err == nil, err) + buf.Flush() + + // decode + recvMsg := initServerRecvMsg() + err = ttHeaderCodec.decode(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) + test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) + + intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() + strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() + test.DeepEqual(t, intKVInfoRecv, intKVInfo) + test.DeepEqual(t, strKVInfoRecv, strKVInfo) + flag := recvMsg.Tags()[HeaderFlagsKey] + test.Assert(t, flag != nil) + test.Assert(t, flag == HeaderFlagSupportOutOfOrder) + }) + } } func TestTTHeaderCodecWithTransInfoWithGDPRToken(t *testing.T) { - ctx := context.Background() - intKVInfo := prepareIntKVInfo() - strKVInfo := prepareStrKVInfoWithGDPRToken() - sendMsg := initClientSendMsg(transport.TTHeader) - sendMsg.TransInfo().PutTransIntInfo(intKVInfo) - sendMsg.TransInfo().PutTransStrInfo(strKVInfo) - sendMsg.Tags()[HeaderFlagsKey] = HeaderFlagSupportOutOfOrder - - // encode - out := remote.NewWriterBuffer(256) - totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, out) - binary.BigEndian.PutUint32(totalLenField, uint32(out.MallocLen()-Size32+mockPayloadLen)) - test.Assert(t, err == nil, err) - - // decode - recvMsg := initServerRecvMsg() - buf, err := out.Bytes() - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = ttHeaderCodec.decode(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) - - intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() - strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() - test.DeepEqual(t, intKVInfoRecv, intKVInfo) - test.DeepEqual(t, strKVInfoRecv, strKVInfo) - flag := recvMsg.Tags()[HeaderFlagsKey] - test.Assert(t, flag != nil) - test.Assert(t, flag == HeaderFlagSupportOutOfOrder) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + ctx := context.Background() + intKVInfo := prepareIntKVInfo() + strKVInfo := prepareStrKVInfoWithGDPRToken() + sendMsg := initClientSendMsg(transport.TTHeader) + sendMsg.TransInfo().PutTransIntInfo(intKVInfo) + sendMsg.TransInfo().PutTransStrInfo(strKVInfo) + sendMsg.Tags()[HeaderFlagsKey] = HeaderFlagSupportOutOfOrder + + // encode + buf := tb.NewBuffer() + totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, buf) + binary.BigEndian.PutUint32(totalLenField, uint32(buf.MallocLen()-Size32+mockPayloadLen)) + test.Assert(t, err == nil, err) + buf.Flush() + + // decode + recvMsg := initServerRecvMsg() + err = ttHeaderCodec.decode(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) + test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) + + intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() + strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() + test.DeepEqual(t, intKVInfoRecv, intKVInfo) + test.DeepEqual(t, strKVInfoRecv, strKVInfo) + flag := recvMsg.Tags()[HeaderFlagsKey] + test.Assert(t, flag != nil) + test.Assert(t, flag == HeaderFlagSupportOutOfOrder) + }) + } } func TestTTHeaderCodecWithTransInfoFromMetaInfoGDPRToken(t *testing.T) { - ctx := context.Background() - intKVInfo := prepareIntKVInfo() - ctx = metainfo.WithValue(ctx, "gdpr-token", "test token") - sendMsg := initClientSendMsg(transport.TTHeader) - sendMsg.TransInfo().PutTransIntInfo(intKVInfo) - ctx, err := tm.MetainfoClientHandler.WriteMeta(ctx, sendMsg) - test.Assert(t, err == nil) - sendMsg.Tags()[HeaderFlagsKey] = HeaderFlagSupportOutOfOrder - - // encode - out := remote.NewWriterBuffer(256) - totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, out) - binary.BigEndian.PutUint32(totalLenField, uint32(out.MallocLen()-Size32+mockPayloadLen)) - test.Assert(t, err == nil, err) - - // decode - recvMsg := initServerRecvMsg() - buf, err := out.Bytes() - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = ttHeaderCodec.decode(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) - - intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() - strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() - test.DeepEqual(t, intKVInfoRecv, intKVInfo) - test.DeepEqual(t, strKVInfoRecv, map[string]string{transmeta.GDPRToken: "test token"}) - flag := recvMsg.Tags()[HeaderFlagsKey] - test.Assert(t, flag != nil) - test.Assert(t, flag == HeaderFlagSupportOutOfOrder) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + ctx := context.Background() + intKVInfo := prepareIntKVInfo() + ctx = metainfo.WithValue(ctx, "gdpr-token", "test token") + sendMsg := initClientSendMsg(transport.TTHeader) + sendMsg.TransInfo().PutTransIntInfo(intKVInfo) + ctx, err := tm.MetainfoClientHandler.WriteMeta(ctx, sendMsg) + test.Assert(t, err == nil) + sendMsg.Tags()[HeaderFlagsKey] = HeaderFlagSupportOutOfOrder + + // encode + buf := tb.NewBuffer() + totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, buf) + binary.BigEndian.PutUint32(totalLenField, uint32(buf.MallocLen()-Size32+mockPayloadLen)) + test.Assert(t, err == nil, err) + buf.Flush() + + // decode + recvMsg := initServerRecvMsg() + err = ttHeaderCodec.decode(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) + test.Assert(t, recvMsg.PayloadLen() == mockPayloadLen, recvMsg.PayloadLen()) + + intKVInfoRecv := recvMsg.TransInfo().TransIntInfo() + strKVInfoRecv := recvMsg.TransInfo().TransStrInfo() + test.DeepEqual(t, intKVInfoRecv, intKVInfo) + test.DeepEqual(t, strKVInfoRecv, map[string]string{transmeta.GDPRToken: "test token"}) + flag := recvMsg.Tags()[HeaderFlagsKey] + test.Assert(t, flag != nil) + test.Assert(t, flag == HeaderFlagSupportOutOfOrder) + }) + } } func TestFillBasicInfoOfTTHeader(t *testing.T) { - ctx := context.Background() - mockAddr := "mock address" - // 1. server side fill from address - // encode - sendMsg := initClientSendMsg(transport.TTHeader) - sendMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] = mockAddr - sendMsg.TransInfo().TransIntInfo()[transmeta.FromService] = mockServiceName - out := remote.NewWriterBuffer(256) - totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, out) - binary.BigEndian.PutUint32(totalLenField, uint32(out.MallocLen()-Size32+mockPayloadLen)) - test.Assert(t, err == nil, err) - // decode - recvMsg := initServerRecvMsg() - buf, err := out.Bytes() - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = ttHeaderCodec.decode(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - test.Assert(t, recvMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] == mockAddr) - test.Assert(t, recvMsg.RPCInfo().From().Address().String() == mockAddr) - test.Assert(t, recvMsg.RPCInfo().From().ServiceName() == mockServiceName) - - // 2. client side fill to address - // encode - sendMsg = initServerSendMsg(transport.TTHeader) - sendMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] = mockAddr - out = remote.NewWriterBuffer(256) - totalLenField, err = ttHeaderCodec.encode(ctx, sendMsg, out) - binary.BigEndian.PutUint32(totalLenField, uint32(out.MallocLen()-Size32+mockPayloadLen)) - test.Assert(t, err == nil, err) - // decode - recvMsg = initClientRecvMsg() - toInfo := remoteinfo.AsRemoteInfo(recvMsg.RPCInfo().To()) - toInfo.SetInstance(&mockInst{}) - buf, err = out.Bytes() - test.Assert(t, err == nil, err) - in = remote.NewReaderBuffer(buf) - err = ttHeaderCodec.decode(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - test.Assert(t, recvMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] == mockAddr) - test.Assert(t, toInfo.Address().String() == mockAddr, toInfo.Address()) + for _, tb := range transportBuffers { + ctx := context.Background() + mockAddr := "mock address" + // 1. server side fill from address + // encode + sendMsg := initClientSendMsg(transport.TTHeader) + sendMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] = mockAddr + sendMsg.TransInfo().TransIntInfo()[transmeta.FromService] = mockServiceName + buf := tb.NewBuffer() + totalLenField, err := ttHeaderCodec.encode(ctx, sendMsg, buf) + binary.BigEndian.PutUint32(totalLenField, uint32(buf.MallocLen()-Size32+mockPayloadLen)) + test.Assert(t, err == nil, err) + buf.Flush() + // decode + recvMsg := initServerRecvMsg() + err = ttHeaderCodec.decode(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) + test.Assert(t, recvMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] == mockAddr) + test.Assert(t, recvMsg.RPCInfo().From().Address().String() == mockAddr) + test.Assert(t, recvMsg.RPCInfo().From().ServiceName() == mockServiceName) + + // 2. client side fill to address + // encode + sendMsg = initServerSendMsg(transport.TTHeader) + sendMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] = mockAddr + buf = tb.NewBuffer() + totalLenField, err = ttHeaderCodec.encode(ctx, sendMsg, buf) + binary.BigEndian.PutUint32(totalLenField, uint32(buf.MallocLen()-Size32+mockPayloadLen)) + test.Assert(t, err == nil, err) + buf.Flush() + // decode + recvMsg = initClientRecvMsg() + toInfo := remoteinfo.AsRemoteInfo(recvMsg.RPCInfo().To()) + toInfo.SetInstance(&mockInst{}) + err = ttHeaderCodec.decode(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) + test.Assert(t, recvMsg.TransInfo().TransStrInfo()[transmeta.HeaderTransRemoteAddr] == mockAddr) + test.Assert(t, toInfo.Address().String() == mockAddr, toInfo.Address()) + } } func BenchmarkTTHeaderCodec(b *testing.B) { diff --git a/pkg/remote/codec/thrift/thrift.go b/pkg/remote/codec/thrift/thrift.go index f99b6055e1..3ec0ed0301 100644 --- a/pkg/remote/codec/thrift/thrift.go +++ b/pkg/remote/codec/thrift/thrift.go @@ -131,6 +131,7 @@ func (c thriftCodec) Marshal(ctx context.Context, message remote.Message, out re // encodeFastThrift encode with the FastCodec way func encodeFastThrift(out remote.ByteBuffer, methodName string, msgType remote.MessageType, seqID int32, msg ThriftMsgFastCodec) error { + nw, _ := out.(remote.NocopyWrite) // nocopy write is a special implementation of linked buffer, only bytebuffer implement NocopyWrite do FastWrite msgBeginLen := bthrift.Binary.MessageBeginLength(methodName, thrift.TMessageType(msgType), seqID) msgEndLen := bthrift.Binary.MessageEndLength() @@ -138,10 +139,17 @@ func encodeFastThrift(out remote.ByteBuffer, methodName string, msgType remote.M if err != nil { return perrors.NewProtocolErrorWithMsg(fmt.Sprintf("thrift marshal, Malloc failed: %s", err.Error())) } + // If fast write enabled, the underlying buffer maybe large than the correct buffer, + // so we need to save the mallocLen before fast write and correct the real mallocLen after codec + mallocLen := out.MallocLen() offset := bthrift.Binary.WriteMessageBegin(buf, methodName, thrift.TMessageType(msgType), seqID) - offset += msg.FastWriteNocopy(buf[offset:], nil) + offset += msg.FastWriteNocopy(buf[offset:], nw) bthrift.Binary.WriteMessageEnd(buf[offset:]) - return nil + if nw == nil { + // if nw is nil, FastWrite will act in Copy mode. + return nil + } + return nw.MallocAck(mallocLen) } // encodeBasicThrift encode with the old thrift way (slow) diff --git a/pkg/remote/codec/thrift/thrift_frugal.go b/pkg/remote/codec/thrift/thrift_frugal.go index 3c8a03ceba..235f2d30c1 100644 --- a/pkg/remote/codec/thrift/thrift_frugal.go +++ b/pkg/remote/codec/thrift/thrift_frugal.go @@ -86,16 +86,20 @@ func (c thriftCodec) hyperMarshal(out remote.ByteBuffer, methodName string, msgT if err != nil { return perrors.NewProtocolErrorWithMsg(fmt.Sprintf("thrift marshal, Malloc failed: %s", err.Error())) } + mallocLen := out.MallocLen() // encode message offset := bthrift.Binary.WriteMessageBegin(buf, methodName, thrift.TMessageType(msgType), seqID) - var writeLen int - writeLen, err = frugal.EncodeObject(buf[offset:], nil, data) + nw, _ := out.(remote.NocopyWrite) + writeLen, err := frugal.EncodeObject(buf[offset:], nw, data) if err != nil { return perrors.NewProtocolErrorWithMsg(fmt.Sprintf("thrift marshal, Encode failed: %s", err.Error())) } offset += writeLen bthrift.Binary.WriteMessageEnd(buf[offset:]) + if nw != nil { + return nw.MallocAck(mallocLen) + } return nil } diff --git a/pkg/remote/codec/thrift/thrift_frugal_test.go b/pkg/remote/codec/thrift/thrift_frugal_test.go index 2d2134a240..f9417f69f9 100644 --- a/pkg/remote/codec/thrift/thrift_frugal_test.go +++ b/pkg/remote/codec/thrift/thrift_frugal_test.go @@ -106,67 +106,76 @@ func TestHyperCodecCheck(t *testing.T) { } func TestFrugalCodec(t *testing.T) { - t.Run("configure frugal but data has not tag", func(t *testing.T) { - ctx := context.Background() - codec := &thriftCodec{FrugalRead | FrugalWrite} + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + t.Run("configure frugal but data has not tag", func(t *testing.T) { + ctx := context.Background() + codec := &thriftCodec{FrugalRead | FrugalWrite} - // MockNoTagArgs cannot be marshaled - sendMsg := initNoTagSendMsg(transport.TTHeader) - out := remote.NewWriterBuffer(256) - err := codec.Marshal(ctx, sendMsg, out) - test.Assert(t, err != nil) - }) - t.Run("configure frugal and data has tag", func(t *testing.T) { - ctx := context.Background() - codec := &thriftCodec{FrugalRead | FrugalWrite} + // MockNoTagArgs cannot be marshaled + sendMsg := initNoTagSendMsg(transport.TTHeader) + out := tb.NewBuffer() + err := codec.Marshal(ctx, sendMsg, out) + test.Assert(t, err != nil) + out.Flush() + }) + t.Run("configure frugal and data has tag", func(t *testing.T) { + ctx := context.Background() + codec := &thriftCodec{FrugalRead | FrugalWrite} - testFrugalDataConversion(t, ctx, codec) - }) - t.Run("fallback to frugal and data has tag", func(t *testing.T) { - ctx := context.Background() - codec := NewThriftCodec() + testFrugalDataConversion(t, ctx, codec) + }) + t.Run("fallback to frugal and data has tag", func(t *testing.T) { + ctx := context.Background() + codec := NewThriftCodec() - testFrugalDataConversion(t, ctx, codec) - }) - t.Run("configure BasicCodec to disable frugal fallback", func(t *testing.T) { - ctx := context.Background() - codec := NewThriftCodecWithConfig(Basic) + testFrugalDataConversion(t, ctx, codec) + }) + t.Run("configure BasicCodec to disable frugal fallback", func(t *testing.T) { + ctx := context.Background() + codec := NewThriftCodecWithConfig(Basic) - // MockNoTagArgs cannot be marshaled - sendMsg := initNoTagSendMsg(transport.TTHeader) - out := remote.NewWriterBuffer(256) - err := codec.Marshal(ctx, sendMsg, out) - test.Assert(t, err != nil) - }) + // MockNoTagArgs cannot be marshaled + sendMsg := initNoTagSendMsg(transport.TTHeader) + out := tb.NewBuffer() + err := codec.Marshal(ctx, sendMsg, out) + out.Flush() + test.Assert(t, err != nil) + }) + }) + } } func testFrugalDataConversion(t *testing.T, ctx context.Context, codec remote.PayloadCodec) { - // encode client side - sendMsg := initFrugalTagSendMsg(transport.TTHeader) - out := remote.NewWriterBuffer(256) - err := codec.Marshal(ctx, sendMsg, out) - test.Assert(t, err == nil, err) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + // encode client side + sendMsg := initFrugalTagSendMsg(transport.TTHeader) + buf := tb.NewBuffer() + err := codec.Marshal(ctx, sendMsg, buf) + test.Assert(t, err == nil, err) + buf.Flush() - // decode server side - recvMsg := initFrugalTagRecvMsg() - buf, err := out.Bytes() - recvMsg.SetPayloadLen(len(buf)) - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = codec.Unmarshal(ctx, recvMsg, in) - test.Assert(t, err == nil, err) + // decode server side + recvMsg := initFrugalTagRecvMsg() + recvMsg.SetPayloadLen(buf.ReadableLen()) + test.Assert(t, err == nil, err) + err = codec.Unmarshal(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) - // compare Args - sendReq := (sendMsg.Data()).(*MockFrugalTagArgs).Req - recvReq := (recvMsg.Data()).(*MockFrugalTagArgs).Req - test.Assert(t, sendReq.Msg == recvReq.Msg) - test.Assert(t, len(sendReq.StrList) == len(recvReq.StrList)) - test.Assert(t, len(sendReq.StrMap) == len(recvReq.StrMap)) - for i, item := range sendReq.StrList { - test.Assert(t, item == recvReq.StrList[i]) - } - for k := range sendReq.StrMap { - test.Assert(t, sendReq.StrMap[k] == recvReq.StrMap[k]) + // compare Args + sendReq := (sendMsg.Data()).(*MockFrugalTagArgs).Req + recvReq := (recvMsg.Data()).(*MockFrugalTagArgs).Req + test.Assert(t, sendReq.Msg == recvReq.Msg) + test.Assert(t, len(sendReq.StrList) == len(recvReq.StrList)) + test.Assert(t, len(sendReq.StrMap) == len(recvReq.StrMap)) + for i, item := range sendReq.StrList { + test.Assert(t, item == recvReq.StrList[i]) + } + for k := range sendReq.StrMap { + test.Assert(t, sendReq.StrMap[k] == recvReq.StrMap[k]) + } + }) } } diff --git a/pkg/remote/codec/thrift/thrift_test.go b/pkg/remote/codec/thrift/thrift_test.go index 7ca41ed317..3d4029b881 100644 --- a/pkg/remote/codec/thrift/thrift_test.go +++ b/pkg/remote/codec/thrift/thrift_test.go @@ -22,11 +22,13 @@ import ( "testing" "github.com/apache/thrift/lib/go/thrift" + "github.com/cloudwego/netpoll" "github.com/cloudwego/kitex/internal/mocks" - mt "github.com/cloudwego/kitex/internal/mocks/thrift" + mt "github.com/cloudwego/kitex/internal/mocks/thrift/fast" "github.com/cloudwego/kitex/internal/test" "github.com/cloudwego/kitex/pkg/remote" + netpolltrans "github.com/cloudwego/kitex/pkg/remote/trans/netpoll" "github.com/cloudwego/kitex/pkg/rpcinfo" "github.com/cloudwego/kitex/pkg/serviceinfo" "github.com/cloudwego/kitex/transport" @@ -35,6 +37,24 @@ import ( var ( payloadCodec = &thriftCodec{FastWrite | FastRead} svcInfo = mocks.ServiceInfo() + + transportBuffers = []struct { + Name string + NewBuffer func() remote.ByteBuffer + }{ + { + Name: "BytesBuffer", + NewBuffer: func() remote.ByteBuffer { + return remote.NewReaderWriterBuffer(1024) + }, + }, + { + Name: "NetpollBuffer", + NewBuffer: func() remote.ByteBuffer { + return netpolltrans.NewReaderWriterByteBuffer(netpoll.NewLinkBuffer(1024)) + }, + }, + } ) func init() { @@ -61,127 +81,138 @@ func (m *mockWithContext) Write(ctx context.Context, oprot thrift.TProtocol) err } func TestWithContext(t *testing.T) { - ctx := context.Background() + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + ctx := context.Background() - req := &mockWithContext{WriteFunc: func(ctx context.Context, oprot thrift.TProtocol) error { - return nil - }} - ink := rpcinfo.NewInvocation("", "mock") - ri := rpcinfo.NewRPCInfo(nil, nil, ink, nil, nil) - msg := remote.NewMessage(req, svcInfo, ri, remote.Call, remote.Client) - msg.SetProtocolInfo(remote.NewProtocolInfo(transport.TTHeader, svcInfo.PayloadCodec)) - out := remote.NewWriterBuffer(256) - err := payloadCodec.Marshal(ctx, msg, out) - test.Assert(t, err == nil, err) - - { - resp := &mockWithContext{ReadFunc: func(ctx context.Context, method string, oprot thrift.TProtocol) error { return nil }} - ink := rpcinfo.NewInvocation("", "mock") - ri := rpcinfo.NewRPCInfo(nil, nil, ink, nil, nil) - msg := remote.NewMessage(resp, svcInfo, ri, remote.Call, remote.Client) - msg.SetProtocolInfo(remote.NewProtocolInfo(transport.TTHeader, svcInfo.PayloadCodec)) - buf, err := out.Bytes() - test.Assert(t, err == nil, err) - msg.SetPayloadLen(len(buf)) - in := remote.NewReaderBuffer(buf) - err = payloadCodec.Unmarshal(ctx, msg, in) - test.Assert(t, err == nil, err) - } -} + req := &mockWithContext{WriteFunc: func(ctx context.Context, oprot thrift.TProtocol) error { + return nil + }} + ink := rpcinfo.NewInvocation("", "mock") + ri := rpcinfo.NewRPCInfo(nil, nil, ink, nil, nil) + msg := remote.NewMessage(req, svcInfo, ri, remote.Call, remote.Client) + msg.SetProtocolInfo(remote.NewProtocolInfo(transport.TTHeader, svcInfo.PayloadCodec)) + buf := tb.NewBuffer() + err := payloadCodec.Marshal(ctx, msg, buf) + test.Assert(t, err == nil, err) + buf.Flush() -func TestNormal(t *testing.T) { - ctx := context.Background() - - // encode client side - sendMsg := initSendMsg(transport.TTHeader) - out := remote.NewWriterBuffer(256) - err := payloadCodec.Marshal(ctx, sendMsg, out) - test.Assert(t, err == nil, err) - - // decode server side - recvMsg := initRecvMsg() - buf, err := out.Bytes() - recvMsg.SetPayloadLen(len(buf)) - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = payloadCodec.Unmarshal(ctx, recvMsg, in) - test.Assert(t, err == nil, err) - - // compare Req Arg - sendReq := (sendMsg.Data()).(*mt.MockTestArgs).Req - recvReq := (recvMsg.Data()).(*mt.MockTestArgs).Req - test.Assert(t, sendReq.Msg == recvReq.Msg) - test.Assert(t, len(sendReq.StrList) == len(recvReq.StrList)) - test.Assert(t, len(sendReq.StrMap) == len(recvReq.StrMap)) - for i, item := range sendReq.StrList { - test.Assert(t, item == recvReq.StrList[i]) - } - for k := range sendReq.StrMap { - test.Assert(t, sendReq.StrMap[k] == recvReq.StrMap[k]) + { + resp := &mockWithContext{ReadFunc: func(ctx context.Context, method string, oprot thrift.TProtocol) error { return nil }} + ink := rpcinfo.NewInvocation("", "mock") + ri := rpcinfo.NewRPCInfo(nil, nil, ink, nil, nil) + msg := remote.NewMessage(resp, svcInfo, ri, remote.Call, remote.Client) + msg.SetProtocolInfo(remote.NewProtocolInfo(transport.TTHeader, svcInfo.PayloadCodec)) + msg.SetPayloadLen(buf.ReadableLen()) + err = payloadCodec.Unmarshal(ctx, msg, buf) + test.Assert(t, err == nil, err) + } + }) } } -func BenchmarkNormalParallel(b *testing.B) { - ctx := context.Background() +func TestNormal(t *testing.T) { + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + ctx := context.Background() - b.ResetTimer() - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - // encode // client side + // encode client side sendMsg := initSendMsg(transport.TTHeader) - out := remote.NewWriterBuffer(256) - err := payloadCodec.Marshal(ctx, sendMsg, out) - test.Assert(b, err == nil, err) + buf := tb.NewBuffer() + err := payloadCodec.Marshal(ctx, sendMsg, buf) + test.Assert(t, err == nil, err) + buf.Flush() // decode server side recvMsg := initRecvMsg() - buf, err := out.Bytes() - recvMsg.SetPayloadLen(len(buf)) - test.Assert(b, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = payloadCodec.Unmarshal(ctx, recvMsg, in) - test.Assert(b, err == nil, err) + recvMsg.SetPayloadLen(buf.ReadableLen()) + test.Assert(t, err == nil, err) + err = payloadCodec.Unmarshal(ctx, recvMsg, buf) + test.Assert(t, err == nil, err) // compare Req Arg sendReq := (sendMsg.Data()).(*mt.MockTestArgs).Req recvReq := (recvMsg.Data()).(*mt.MockTestArgs).Req - test.Assert(b, sendReq.Msg == recvReq.Msg) - test.Assert(b, len(sendReq.StrList) == len(recvReq.StrList)) - test.Assert(b, len(sendReq.StrMap) == len(recvReq.StrMap)) + test.Assert(t, sendReq.Msg == recvReq.Msg) + test.Assert(t, len(sendReq.StrList) == len(recvReq.StrList)) + test.Assert(t, len(sendReq.StrMap) == len(recvReq.StrMap)) for i, item := range sendReq.StrList { - test.Assert(b, item == recvReq.StrList[i]) + test.Assert(t, item == recvReq.StrList[i]) } for k := range sendReq.StrMap { - test.Assert(b, sendReq.StrMap[k] == recvReq.StrMap[k]) + test.Assert(t, sendReq.StrMap[k] == recvReq.StrMap[k]) } - } - }) + }) + } +} + +func BenchmarkNormalParallel(b *testing.B) { + for _, tb := range transportBuffers { + b.Run(tb.Name, func(b *testing.B) { + ctx := context.Background() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + // encode // client side + sendMsg := initSendMsg(transport.TTHeader) + buf := tb.NewBuffer() + err := payloadCodec.Marshal(ctx, sendMsg, buf) + test.Assert(b, err == nil, err) + buf.Flush() + + // decode server side + recvMsg := initRecvMsg() + recvMsg.SetPayloadLen(buf.ReadableLen()) + test.Assert(b, err == nil, err) + err = payloadCodec.Unmarshal(ctx, recvMsg, buf) + test.Assert(b, err == nil, err) + + // compare Req Arg + sendReq := (sendMsg.Data()).(*mt.MockTestArgs).Req + recvReq := (recvMsg.Data()).(*mt.MockTestArgs).Req + test.Assert(b, sendReq.Msg == recvReq.Msg) + test.Assert(b, len(sendReq.StrList) == len(recvReq.StrList)) + test.Assert(b, len(sendReq.StrMap) == len(recvReq.StrMap)) + for i, item := range sendReq.StrList { + test.Assert(b, item == recvReq.StrList[i]) + } + for k := range sendReq.StrMap { + test.Assert(b, sendReq.StrMap[k] == recvReq.StrMap[k]) + } + } + }) + }) + } } func TestException(t *testing.T) { - ctx := context.Background() - ink := rpcinfo.NewInvocation("", "mock") - ri := rpcinfo.NewRPCInfo(nil, nil, ink, nil, nil) - errInfo := "mock exception" - transErr := remote.NewTransErrorWithMsg(remote.UnknownMethod, errInfo) - // encode server side - errMsg := initServerErrorMsg(transport.TTHeader, ri, transErr) - out := remote.NewWriterBuffer(256) - err := payloadCodec.Marshal(ctx, errMsg, out) - test.Assert(t, err == nil, err) - - // decode client side - recvMsg := initClientRecvMsg(ri) - buf, err := out.Bytes() - recvMsg.SetPayloadLen(len(buf)) - test.Assert(t, err == nil, err) - in := remote.NewReaderBuffer(buf) - err = payloadCodec.Unmarshal(ctx, recvMsg, in) - test.Assert(t, err != nil) - transErr, ok := err.(*remote.TransError) - test.Assert(t, ok) - test.Assert(t, err.Error() == errInfo) - test.Assert(t, transErr.TypeID() == remote.UnknownMethod) + for _, tb := range transportBuffers { + t.Run(tb.Name, func(t *testing.T) { + ctx := context.Background() + ink := rpcinfo.NewInvocation("", "mock") + ri := rpcinfo.NewRPCInfo(nil, nil, ink, nil, nil) + errInfo := "mock exception" + transErr := remote.NewTransErrorWithMsg(remote.UnknownMethod, errInfo) + // encode server side + errMsg := initServerErrorMsg(transport.TTHeader, ri, transErr) + buf := tb.NewBuffer() + err := payloadCodec.Marshal(ctx, errMsg, buf) + test.Assert(t, err == nil, err) + buf.Flush() + + // decode client side + recvMsg := initClientRecvMsg(ri) + recvMsg.SetPayloadLen(buf.ReadableLen()) + test.Assert(t, err == nil, err) + err = payloadCodec.Unmarshal(ctx, recvMsg, buf) + test.Assert(t, err != nil) + transErr, ok := err.(*remote.TransError) + test.Assert(t, ok) + test.Assert(t, err.Error() == errInfo) + test.Assert(t, transErr.TypeID() == remote.UnknownMethod) + }) + } } func TestTransErrorUnwrap(t *testing.T) { diff --git a/pkg/remote/trans/netpoll/bytebuf.go b/pkg/remote/trans/netpoll/bytebuf.go index a00f2cd174..736060da97 100644 --- a/pkg/remote/trans/netpoll/bytebuf.go +++ b/pkg/remote/trans/netpoll/bytebuf.go @@ -167,6 +167,14 @@ func (b *netpollByteBuffer) Malloc(n int) (buf []byte, err error) { return b.writer.Malloc(n) } +// MallocAck n bytes in the writer buffer. +func (b *netpollByteBuffer) MallocAck(n int) (err error) { + if b.status&remote.BitWritable == 0 { + return errors.New("unwritable buffer, cannot support MallocAck") + } + return b.writer.MallocAck(n) +} + // MallocLen returns the total length of the buffer malloced. func (b *netpollByteBuffer) MallocLen() (length int) { if b.status&remote.BitWritable == 0 { @@ -239,6 +247,9 @@ func (b *netpollByteBuffer) AppendBuffer(buf remote.ByteBuffer) (err error) { // Bytes are not supported in netpoll bytebuf. func (b *netpollByteBuffer) Bytes() (buf []byte, err error) { + if b.reader != nil { + return b.reader.Peek(b.reader.Len()) + } return nil, errors.New("method Bytes() not support in netpoll bytebuf") } From e822943e004aa2f1b40104c38e963f60c7cc197e Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Wed, 24 Apr 2024 18:52:51 +0800 Subject: [PATCH 21/49] feat(tool): fast-codec supports Thrift Fieldmask (#1336) --- .gitignore | 3 + tool/internal_pkg/generator/generator.go | 11 +- tool/internal_pkg/generator/generator_test.go | 1 + tool/internal_pkg/pluginmode/thriftgo/ast.go | 94 ++++++ .../pluginmode/thriftgo/file_tpl.go | 66 ++-- .../pluginmode/thriftgo/patcher.go | 130 ++++--- .../pluginmode/thriftgo/struct_tpl.go | 317 +++++++++++++++--- tool/internal_pkg/util/util.go | 61 ++++ 8 files changed, 557 insertions(+), 126 deletions(-) create mode 100644 tool/internal_pkg/pluginmode/thriftgo/ast.go diff --git a/.gitignore b/.gitignore index d9b39ede2c..f3872df9e2 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ tool/cmd/kitex/kitex # remote dump file *.json +base1.go +dump.txt +base2.go diff --git a/tool/internal_pkg/generator/generator.go b/tool/internal_pkg/generator/generator.go index 44de6a031c..5adbfe6cf9 100644 --- a/tool/internal_pkg/generator/generator.go +++ b/tool/internal_pkg/generator/generator.go @@ -54,11 +54,12 @@ var ( globalMiddlewares []Middleware globalDependencies = map[string]string{ - "kitex": kitexImportPath, - "client": ImportPathTo("client"), - "server": ImportPathTo("server"), - "callopt": ImportPathTo("client/callopt"), - "frugal": "github.com/cloudwego/frugal", + "kitex": kitexImportPath, + "client": ImportPathTo("client"), + "server": ImportPathTo("server"), + "callopt": ImportPathTo("client/callopt"), + "frugal": "github.com/cloudwego/frugal", + "fieldmask": "github.com/cloudwego/thriftgo/fieldmask", } ) diff --git a/tool/internal_pkg/generator/generator_test.go b/tool/internal_pkg/generator/generator_test.go index 2275568a32..f71772a4a6 100644 --- a/tool/internal_pkg/generator/generator_test.go +++ b/tool/internal_pkg/generator/generator_test.go @@ -59,6 +59,7 @@ func TestConfig_Pack(t *testing.T) { Protocol string HandlerReturnKeepResp bool } + tests := []struct { name string fields fields diff --git a/tool/internal_pkg/pluginmode/thriftgo/ast.go b/tool/internal_pkg/pluginmode/thriftgo/ast.go new file mode 100644 index 0000000000..624e5c3280 --- /dev/null +++ b/tool/internal_pkg/pluginmode/thriftgo/ast.go @@ -0,0 +1,94 @@ +// Copyright 2023 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package thriftgo + +import ( + "github.com/cloudwego/thriftgo/generator/golang" + "github.com/cloudwego/thriftgo/parser" +) + +func offsetTPL(assign string, offset string) string { + return offset + " += " + assign + "\n" +} + +func ZeroWriter(t *parser.Type, oprot string, buf string, offset string) string { + switch t.GetCategory() { + case parser.Category_Bool: + return offsetTPL(oprot+".WriteBool("+buf+", false)", offset) + case parser.Category_Byte: + return offsetTPL(oprot+".WriteByte("+buf+", 0)", offset) + case parser.Category_I16: + return offsetTPL(oprot+".WriteI16("+buf+", 0)", offset) + case parser.Category_Enum, parser.Category_I32: + return offsetTPL(oprot+".WriteI32("+buf+", 0)", offset) + case parser.Category_I64: + return offsetTPL(oprot+".WriteI64("+buf+", 0)", offset) + case parser.Category_Double: + return offsetTPL(oprot+".WriteDouble("+buf+", 0)", offset) + case parser.Category_String: + return offsetTPL(oprot+".WriteString("+buf+", \"\")", offset) + case parser.Category_Binary: + return offsetTPL(oprot+".WriteBinary("+buf+", []byte{})", offset) + case parser.Category_Map: + return offsetTPL(oprot+".WriteMapBegin("+buf+", thrift."+golang.GetTypeIDConstant(t.GetKeyType())+ + ",thrift."+golang.GetTypeIDConstant(t.GetValueType())+",0)", offset) + offsetTPL(oprot+".WriteMapEnd("+buf+")", offset) + case parser.Category_List: + return offsetTPL(oprot+".WriteListBegin("+buf+", thrift."+golang.GetTypeIDConstant(t.GetValueType())+ + ",0)", offset) + offsetTPL(oprot+".WriteListEnd("+buf+")", offset) + case parser.Category_Set: + return offsetTPL(oprot+".WriteSetBegin("+buf+", thrift."+golang.GetTypeIDConstant(t.GetValueType())+ + ",0)", offset) + offsetTPL(oprot+".WriteSetEnd("+buf+")", offset) + case parser.Category_Struct: + return offsetTPL(oprot+".WriteStructBegin("+buf+", \"\")", offset) + offsetTPL(oprot+".WriteFieldStop("+buf+")", offset) + + offsetTPL(oprot+".WriteStructEnd("+buf+")", offset) + default: + panic("unsupported type zero writer for" + t.Name) + } +} + +func ZeroBLength(t *parser.Type, oprot string, offset string) string { + switch t.GetCategory() { + case parser.Category_Bool: + return offsetTPL(oprot+".BoolLength(false)", offset) + case parser.Category_Byte: + return offsetTPL(oprot+".ByteLength(0)", offset) + case parser.Category_I16: + return offsetTPL(oprot+".I16Length(0)", offset) + case parser.Category_Enum, parser.Category_I32: + return offsetTPL(oprot+".I32Length(0)", offset) + case parser.Category_I64: + return offsetTPL(oprot+".I64Length(0)", offset) + case parser.Category_Double: + return offsetTPL(oprot+".DoubleLength(0)", offset) + case parser.Category_String: + return offsetTPL(oprot+".StringLength(\"\")", offset) + case parser.Category_Binary: + return offsetTPL(oprot+".BinaryLength([]byte{})", offset) + case parser.Category_Map: + return offsetTPL(oprot+".MapBeginLength(thrift."+golang.GetTypeIDConstant(t.GetKeyType())+ + ",thrift."+golang.GetTypeIDConstant(t.GetValueType())+", 0)", offset) + offsetTPL(oprot+".MapEndLength()", offset) + case parser.Category_List: + return offsetTPL(oprot+".ListBeginLength(thrift."+golang.GetTypeIDConstant(t.GetValueType())+ + ",0)", offset) + offsetTPL(oprot+".ListEndLength()", offset) + case parser.Category_Set: + return offsetTPL(oprot+".SetBeginLength(thrift."+golang.GetTypeIDConstant(t.GetValueType())+ + ",0)", offset) + offsetTPL(oprot+".SetEndLength()", offset) + case parser.Category_Struct: + return offsetTPL(oprot+".StructBeginLength(\"\")", offset) + offsetTPL(oprot+".FieldStopLength()", offset) + + offsetTPL(oprot+".StructEndLength()", offset) + default: + panic("unsupported type zero writer for" + t.Name) + } +} diff --git a/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go b/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go index cbd7985cd8..ce94dd136a 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go +++ b/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go @@ -20,46 +20,25 @@ const file = ` package {{.PkgName}} -import ( - "fmt" - "bytes" - "strings" - "reflect" - - "github.com/apache/thrift/lib/go/thrift" - - {{if GenerateDeepCopyAPIs -}} - kutils "github.com/cloudwego/kitex/pkg/utils" - {{- end}} - {{if GenerateFastAPIs}} - "{{ImportPathTo "pkg/protocol/bthrift"}}" - {{- end}} - {{if GenerateArgsResultTypes}} - {{if Features.KeepUnknownFields}} - {{- if ne (len .Scope.Services) 0}} - unknown "github.com/cloudwego/thriftgo/generator/golang/extension/unknown" - {{- end}} - {{- end}} - {{- end}} - {{- range $path, $alias := .Imports}} - {{$alias }}"{{$path}}" - {{- end}} -) +` + importInsertPoint + ` {{InsertionPoint "KitexUnusedProtection"}} - // unused protection var ( - _ = fmt.Formatter(nil) - _ = (*bytes.Buffer)(nil) - _ = (*strings.Builder)(nil) - _ = reflect.Type(nil) - _ = thrift.TProtocol(nil) + _ = fmt.Formatter(nil) {{- UseStdLibrary "fmt"}} + _ = (*bytes.Buffer)(nil) {{- UseStdLibrary "bytes"}} + _ = (*strings.Builder)(nil) {{- UseStdLibrary "strings"}} + _ = reflect.Type(nil) {{- UseStdLibrary "reflect"}} + _ = thrift.TProtocol(nil) {{- UseStdLibrary "thrift"}} {{- if GenerateFastAPIs}} + {{- UseLib (ImportPathTo "pkg/protocol/bthrift") ""}} _ = bthrift.BinaryWriter(nil) {{- end}} - {{- range .Imports | ToPackageNames}} - _ = {{.}}.KitexUnusedProtection + {{- if GenerateDeepCopyAPIs}} + {{- UseLib "github.com/cloudwego/kitex/pkg/utils" "kutils"}} + {{- end}} + {{- if and (and GenerateArgsResultTypes Features.KeepUnknownFields) (ne (len .Scope.Services) 0)}} + {{- UseStdLibrary "unknown"}} {{- end}} ) @@ -68,6 +47,22 @@ var ( {{end}}{{/* define "file" */}} ` +const imports = ` +{{define "imports"}} +import ( + {{PrintImports .Imports}} +) + +{{- if gt (len .Includes) 0}} +var ( + {{- range .Includes }} + _ = {{.PackageName}}.KitexUnusedProtection + {{- end}} +) +{{- end}} +{{end}}{{/* define "imports" */}} +` + const body = ` {{define "body"}} @@ -95,7 +90,9 @@ const patchArgsAndResult = ` {{- if GenerateArgsResultTypes}} +{{- $withFieldMask := (SetWithFieldMask false) }} {{template "StructLike" $argType}} +{{- $_ := (SetWithFieldMask $withFieldMask) }} {{- end}}{{/* if GenerateArgsResultTypes */}} func (p *{{$argType.GoName}}) GetFirstArgument() interface{} { return {{if .Arguments}}p.{{(index $argType.Fields 0).GoName}}{{else}}nil{{end}} @@ -103,7 +100,9 @@ func (p *{{$argType.GoName}}) GetFirstArgument() interface{} { {{if not .Oneway}} {{- if GenerateArgsResultTypes}} +{{- $withFieldMask := (SetWithFieldMask false) }} {{template "StructLike" $resType}} +{{- $_ := (SetWithFieldMask $withFieldMask) }} {{- end}}{{/* if GenerateArgsResultTypes */}} func (p *{{$resType.GoName}}) GetResult() interface{} { return {{if .Void}}nil{{else}}p.Success{{end}} @@ -118,6 +117,7 @@ func (p *{{$resType.GoName}}) GetResult() interface{} { var basicTemplates = []string{ patchArgsAndResult, file, + imports, body, registerHessian, } diff --git a/tool/internal_pkg/pluginmode/thriftgo/patcher.go b/tool/internal_pkg/pluginmode/thriftgo/patcher.go index 18adb16cc0..d89f017773 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/patcher.go +++ b/tool/internal_pkg/pluginmode/thriftgo/patcher.go @@ -20,7 +20,6 @@ import ( "path/filepath" "reflect" "runtime" - "sort" "strconv" "strings" "text/template" @@ -63,10 +62,23 @@ type patcher struct { handlerReturnKeepResp bool fileTpl *template.Template + libs map[string]string +} + +func (p *patcher) UseLib(path string, alias string) string { + if p.libs == nil { + p.libs = make(map[string]string) + } + p.libs[path] = alias + return "" } func (p *patcher) buildTemplates() (err error) { m := p.utils.BuildFuncMap() + m["PrintImports"] = util.PrintlImports + m["UseLib"] = p.UseLib + m["ZeroWriter"] = ZeroWriter + m["ZeroBLength"] = ZeroBLength m["ReorderStructFields"] = p.reorderStructFields m["TypeIDToGoType"] = func(t string) string { return typeIDToGoType[t] } m["IsBinaryOrStringType"] = p.isBinaryOrStringType @@ -75,17 +87,6 @@ func (p *patcher) buildTemplates() (err error) { m["GenerateDeepCopyAPIs"] = func() bool { return p.deepCopyAPI } m["GenerateArgsResultTypes"] = func() bool { return p.utils.Template() == "slim" } m["ImportPathTo"] = generator.ImportPathTo - m["ToPackageNames"] = func(imports map[string]string) (res []string) { - for pth, alias := range imports { - if alias != "" { - res = append(res, alias) - } else { - res = append(res, strings.ToLower(filepath.Base(pth))) - } - } - sort.Strings(res) - return - } m["Str"] = func(id int32) string { if id < 0 { return "_" + strconv.Itoa(-int(id)) @@ -183,8 +184,18 @@ func (p *patcher) buildTemplates() (err error) { processor, ) } - for _, txt := range allTemplates { - tpl = template.Must(tpl.Parse(txt)) + for i, txt := range allTemplates { + tpl, err = tpl.Parse(txt) + if err != nil { + name := strconv.Itoa(i) + if ix := strings.Index(txt, "{{define "); ix >= 0 { + ex := strings.Index(txt, "}}") + if ex >= 0 { + name = txt[ix+len("{{define ") : ex] + } + } + return fmt.Errorf("parse template %s failed: %v", name, err) + } } ext := `{{define "ExtraTemplates"}}{{end}}` @@ -208,20 +219,28 @@ func (p *patcher) buildTemplates() (err error) { return nil } +const importInsertPoint = "// imports insert-point" + func (p *patcher) patch(req *plugin.Request) (patches []*plugin.Generated, err error) { - p.buildTemplates() + if err := p.buildTemplates(); err != nil { + return nil, err + } + var buf strings.Builder + // fd, _ := os.OpenFile("dump.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + // defer fd.Close() + protection := make(map[string]*plugin.Generated) for ast := range req.AST.DepthFirstSearch() { + // fd.WriteString(p.utils.GetFilename(ast) + "\n") // scope, err := golang.BuildScope(p.utils, ast) scope, _, err := golang.BuildRefScope(p.utils, ast) if err != nil { return nil, fmt.Errorf("build scope for ast %q: %w", ast.Filename, err) } p.utils.SetRootScope(scope) - pkgName := golang.GetImportPackage(golang.GetImportPath(p.utils, ast)) path := p.utils.CombineOutputPath(req.OutputPath, ast) @@ -259,31 +278,51 @@ func (p *patcher) patch(req *plugin.Request) (patches []*plugin.Generated, err e } data := &struct { - Scope *golang.Scope - PkgName string - Imports map[string]string + Scope *golang.Scope + PkgName string + Imports []util.Import + Includes []util.Import }{Scope: scope, PkgName: pkgName} - data.Imports, err = scope.ResolveImports() - if err != nil { - return nil, fmt.Errorf("resolve imports failed for %q: %w", ast.Filename, err) - } - p.filterStdLib(data.Imports) + if err = p.fileTpl.ExecuteTemplate(&buf, "file", data); err != nil { return nil, fmt.Errorf("%q: %w", ast.Filename, err) } content := buf.String() + // if kutils is not used, remove the dependency. + // OPT: use UseStdLib tmpl func if !strings.Contains(content, "kutils.StringDeepCopy") { - kutilsImp := `kutils "github.com/cloudwego/kitex/pkg/utils"` - idx := strings.Index(content, kutilsImp) - if idx > 0 { - content = content[:idx-1] + content[idx+len(kutilsImp):] - } + delete(p.libs, "github.com/cloudwego/kitex/pkg/utils") + } + + imps, err := scope.ResolveImports() + if err != nil { + return nil, fmt.Errorf("resolve imports failed for %q: %w", ast.Filename, err) } + for path, alias := range p.libs { + imps[path] = alias + } + data.Imports = util.SortImports(imps, p.module) + data.Includes = p.extractLocalLibs(data.Imports) + + // replace imports insert-pointer with newly rendered output + buf.Reset() + if err = p.fileTpl.ExecuteTemplate(&buf, "imports", data); err != nil { + return nil, fmt.Errorf("%q: %w", ast.Filename, err) + } + imports := buf.String() + if i := strings.Index(content, importInsertPoint); i >= 0 { + content = strings.Replace(content, importInsertPoint, imports, 1) + } else { + return nil, fmt.Errorf("replace imports failed") + } + patches = append(patches, &plugin.Generated{ Content: content, Name: &target, }) + // fd.WriteString("patched: " + target + "\n") + // fd.WriteString("content: " + content + "\nend\n") if p.copyIDL { content, err := ioutil.ReadFile(ast.Filename) @@ -343,6 +382,20 @@ func getBashPath() string { return "kitex-all.sh" } +func (p *patcher) extractLocalLibs(imports []util.Import) []util.Import { + var ret = make([]util.Import, 0) + prefix := p.module + "/" + // remove std libs and thrift to prevent duplicate import. + for _, v := range imports { + // local packages + if strings.HasPrefix(v.Path, prefix) { + ret = append(ret, v) + continue + } + } + return ret +} + // DoRecord records current cmd into kitex-all.sh func doRecord(recordCmd []string) string { bytes, err := ioutil.ReadFile(getBashPath()) @@ -404,25 +457,6 @@ func (p *patcher) reorderStructFields(fields []*golang.Field) ([]*golang.Field, return sortedFields, nil } -func (p *patcher) filterStdLib(imports map[string]string) { - // remove std libs and thrift to prevent duplicate import. - prefix := p.module + "/" - for pth := range imports { - if strings.HasPrefix(pth, prefix) { // local module - continue - } - if pth == "github.com/apache/thrift/lib/go/thrift" { - delete(imports, pth) - } - if strings.HasPrefix(pth, "github.com/cloudwego/thriftgo") { - delete(imports, pth) - } - if !strings.Contains(pth, ".") { // std lib - delete(imports, pth) - } - } -} - func (p *patcher) isBinaryOrStringType(t *parser.Type) bool { return t.Category.IsBinary() || t.Category.IsString() } diff --git a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go index 71cf89dd1c..8a23b8612b 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go +++ b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go @@ -171,14 +171,27 @@ const structLikeFastReadField = ` {{- $TypeName := .GoName}} {{- range .Fields}} {{$FieldName := .GoName}} +{{- $isBaseVal := .Type | IsBaseType}} func (p *{{$TypeName}}) FastReadField{{Str .ID}}(buf []byte) (int, error) { offset := 0 - {{- $ctx := MkRWCtx .}} - {{- $target := print $ctx.Target }} - {{- $ctx = $ctx.WithDecl.WithTarget "_field"}} - {{ template "FieldFastRead" $ctx}} - {{/* line break */}} - {{- $target}} = _field + {{- if Features.WithFieldMask}} + if {{if $isBaseVal}}_{{else}}fm{{end}}, ex := p._fieldmask.Field({{.ID}}); ex { + {{- end}} + {{- $ctx := (MkRWCtx .).WithFieldMask "fm" -}} + {{- $target := print $ctx.Target }} + {{- $ctx = $ctx.WithDecl.WithTarget "_field"}} + {{ template "FieldFastRead" $ctx}} + {{/* line break */}} + {{- $target}} = _field + {{- if Features.WithFieldMask}} + } else { + l, err := bthrift.Binary.Skip(buf[offset:], thrift.{{.Type | GetTypeIDConstant}}) + offset += l + if err != nil { + return offset, err + } + } + {{- end}} return offset, nil } {{- end}}{{/* range .Fields */}} @@ -266,8 +279,9 @@ func (p *{{$TypeName}}) BLength() int { l += bthrift.Binary.StructBeginLength("{{.Name}}") if p != nil { {{- range .Fields}} + {{- $isBaseVal := .Type | IsBaseType}} l += p.field{{Str .ID}}Length() - {{- end}} + {{- end}}{{/* range.Fields */}} {{- if Features.KeepUnknownFields}} l += len(p._unknownFields) {{- end}}{{/* if Features.KeepUnknownFields */}} @@ -289,15 +303,36 @@ const structLikeFastWriteField = ` {{- range .Fields}} {{- $FieldName := .GoName}} {{- $TypeID := .Type | GetTypeIDConstant }} +{{- $isBaseVal := .Type | IsBaseType}} func (p *{{$TypeName}}) fastWriteField{{Str .ID}}(buf []byte, binaryWriter bthrift.BinaryWriter) int { offset := 0 {{- if .Requiredness.IsOptional}} if p.{{.IsSetter}}() { {{- end}} - offset += bthrift.Binary.WriteFieldBegin(buf[offset:], "{{.Name}}", thrift.{{$TypeID}}, {{.ID}}) - {{- $ctx := MkRWCtx .}} - {{- template "FieldFastWrite" $ctx}} - offset += bthrift.Binary.WriteFieldEnd(buf[offset:]) + {{- if Features.WithFieldMask}} + {{- if and .Requiredness.IsRequired (not Features.FieldMaskZeroRequired)}} + {{- if not $isBaseVal}} + fm, _ := p._fieldmask.Field({{.ID}}) + {{- end}} + {{- else}} + if {{if $isBaseVal}}_{{else}}fm{{end}}, ex := p._fieldmask.Field({{.ID}}); ex { + {{- end}} + {{- end}} + offset += bthrift.Binary.WriteFieldBegin(buf[offset:], "{{.Name}}", thrift.{{$TypeID}}, {{.ID}}) + {{- $ctx := (MkRWCtx .).WithFieldMask "fm"}} + {{- template "FieldFastWrite" $ctx}} + offset += bthrift.Binary.WriteFieldEnd(buf[offset:]) + {{- if Features.WithFieldMask}} + {{- if Features.FieldMaskZeroRequired}} + } else { + offset += bthrift.Binary.WriteFieldBegin(buf[offset:], "{{.Name}}", thrift.{{$TypeID}}, {{.ID}}) + {{ ZeroWriter .Type "bthrift.Binary" "buf[offset:]" "offset" -}} + offset += bthrift.Binary.WriteFieldEnd(buf[offset:]) + } + {{- else if not .Requiredness.IsRequired}} + } + {{- end}} + {{- end}} {{- if .Requiredness.IsOptional}} } {{- end}} @@ -313,15 +348,36 @@ const structLikeFieldLength = ` {{- range .Fields}} {{- $FieldName := .GoName}} {{- $TypeID := .Type | GetTypeIDConstant }} +{{- $isBaseVal := .Type | IsBaseType}} func (p *{{$TypeName}}) field{{Str .ID}}Length() int { l := 0 {{- if .Requiredness.IsOptional}} if p.{{.IsSetter}}() { {{- end}} - l += bthrift.Binary.FieldBeginLength("{{.Name}}", thrift.{{$TypeID}}, {{.ID}}) - {{- $ctx := MkRWCtx .}} - {{- template "FieldLength" $ctx}} - l += bthrift.Binary.FieldEndLength() + {{- if Features.WithFieldMask}} + {{- if and .Requiredness.IsRequired (not Features.FieldMaskZeroRequired)}} + {{- if not $isBaseVal}} + fm, _ := p._fieldmask.Field({{.ID}}) + {{- end}} + {{- else}} + if {{if $isBaseVal}}_{{else}}fm{{end}}, ex := p._fieldmask.Field({{.ID}}); ex { + {{- end}} + {{- end}} + l += bthrift.Binary.FieldBeginLength("{{.Name}}", thrift.{{$TypeID}}, {{.ID}}) + {{- $ctx := (MkRWCtx .).WithFieldMask "fm"}} + {{- template "FieldLength" $ctx}} + l += bthrift.Binary.FieldEndLength() + {{- if Features.WithFieldMask}} + {{- if Features.FieldMaskZeroRequired}} + } else { + l += bthrift.Binary.FieldBeginLength("{{.Name}}", thrift.{{$TypeID}}, {{.ID}}) + {{ ZeroBLength .Type "bthrift.Binary" "l" -}} + l += bthrift.Binary.FieldEndLength() + } + {{- else if not .Requiredness.IsRequired}} + } + {{- end}} + {{- end}} {{- if .Requiredness.IsOptional}} } {{- end}} @@ -348,6 +404,13 @@ const fieldFastReadStructLike = ` {{- if .NeedDecl}} {{- .Target}} := {{.TypeName.Deref.NewFunc}}() {{- end}} + {{- if and (Features.WithFieldMask) .NeedFieldMask}} + {{- if Features.FieldMaskHalfway}} + {{.Target}}.Pass_FieldMask({{.FieldMask}}) + {{- else}} + {{.Target}}.Set_FieldMask({{.FieldMask}}) + {{- end}} + {{- end}} if l, err := {{- .Target}}.FastRead(buf[offset:]); err != nil { return offset, err } else { @@ -399,6 +462,10 @@ const fieldFastReadContainer = ` const fieldFastReadMap = ` {{define "FieldFastReadMap"}} {{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} +{{- $isIntKey := .KeyCtx.Type | IsIntType -}} +{{- $isStrKey := .KeyCtx.Type | IsStrType -}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := "nfm"}} _, _, size, l, err := bthrift.Binary.ReadMapBegin(buf[offset:]) offset += l if err != nil { @@ -410,23 +477,54 @@ const fieldFastReadMap = ` {{- end}} for i := 0; i < size; i++ { {{- $key := .GenID "_key"}} - {{- $ctx := .KeyCtx.WithDecl.WithTarget $key}} + {{- $ctx := (.KeyCtx.WithDecl.WithTarget $key).WithFieldMask ""}} {{- template "FieldFastRead" $ctx}} + {{- if Features.WithFieldMask}} + {{- if $isIntKey}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(int({{$key}})); !ex { + l, err := bthrift.Binary.Skip(buf[offset:], thrift.{{.ValCtx.Type | GetTypeIDConstant}}) + offset += l + if err != nil { + return offset, err + } + continue + } else { + {{- else if $isStrKey}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Str(string({{$key}})); !ex { + l, err := bthrift.Binary.Skip(buf[offset:], thrift.{{.ValCtx.Type | GetTypeIDConstant}}) + offset += l + if err != nil { + return offset, err + } + continue + } else { + {{- else}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(0); !ex { + l, err := bthrift.Binary.Skip(buf[offset:], thrift.{{.ValCtx.Type | GetTypeIDConstant}}) + offset += l + if err != nil { + return offset, err + } + continue + } else { + {{- end}} + {{- end}}{{/* end WithFieldMask */}} {{/* line break */}} {{- $val := .GenID "_val"}} - {{- $ctx := .ValCtx.WithTarget $val}} + {{- $ctx := (.ValCtx.WithTarget $val).WithFieldMask $curFieldMask}} {{- if $isStructVal}} {{$val}} := &values[i] {{- else}} {{- $ctx = $ctx.WithDecl}} {{- end}} {{- template "FieldFastRead" $ctx}} - {{if and .ValCtx.Type.Category.IsStructLike Features.ValueTypeForSIC}} {{$val = printf "*%s" $val}} {{end}} - {{.Target}}[{{$key}}] = {{$val}} + {{- if and Features.WithFieldMask}} + } + {{- end}} } if l, err := bthrift.Binary.ReadMapEnd(buf[offset:]); err != nil { return offset, err @@ -439,6 +537,8 @@ const fieldFastReadMap = ` const fieldFastReadSet = ` {{define "FieldFastReadSet"}} {{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := .FieldMask}} _, size, l, err := bthrift.Binary.ReadSetBegin(buf[offset:]) offset += l if err != nil { @@ -450,7 +550,18 @@ const fieldFastReadSet = ` {{- end}} for i := 0; i < size; i++ { {{- $val := .GenID "_elem"}} - {{- $ctx := .ValCtx.WithTarget $val}} + {{- if Features.WithFieldMask}} + {{- $curFieldMask = "nfm"}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(i); !ex { + l, err := bthrift.Binary.Skip(buf[offset:], thrift.{{.ValCtx.Type | GetTypeIDConstant}}) + offset += l + if err != nil { + return offset, err + } + continue + } else { + {{- end}} + {{- $ctx := (.ValCtx.WithTarget $val).WithFieldMask $curFieldMask}} {{- if $isStructVal}} {{$val}} := &values[i] {{- else}} @@ -461,6 +572,9 @@ const fieldFastReadSet = ` {{$val = printf "*%s" $val}} {{end}} {{.Target}} = append({{.Target}}, {{$val}}) + {{- if Features.WithFieldMask}} + } + {{- end}} } if l, err := bthrift.Binary.ReadSetEnd(buf[offset:]); err != nil { return offset, err @@ -473,6 +587,8 @@ const fieldFastReadSet = ` const fieldFastReadList = ` {{define "FieldFastReadList"}} {{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := .FieldMask}} _, size, l, err := bthrift.Binary.ReadListBegin(buf[offset:]) offset += l if err != nil { @@ -484,7 +600,18 @@ const fieldFastReadList = ` {{- end}} for i := 0; i < size; i++ { {{- $val := .GenID "_elem"}} - {{- $ctx := .ValCtx.WithTarget $val}} + {{- if Features.WithFieldMask}} + {{- $curFieldMask = "nfm"}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(i); !ex { + l, err := bthrift.Binary.Skip(buf[offset:], thrift.{{.ValCtx.Type | GetTypeIDConstant}}) + offset += l + if err != nil { + return offset, err + } + continue + } else { + {{- end}} + {{- $ctx := (.ValCtx.WithTarget $val).WithFieldMask $curFieldMask}} {{- if $isStructVal}} {{$val}} := &values[i] {{- else}} @@ -495,6 +622,9 @@ const fieldFastReadList = ` {{$val = printf "*%s" $val}} {{end}} {{.Target}} = append({{.Target}}, {{$val}}) + {{- if Features.WithFieldMask}} + } + {{- end}} } if l, err := bthrift.Binary.ReadListEnd(buf[offset:]); err != nil { return offset, err @@ -677,12 +807,26 @@ const fieldLength = ` const fieldFastWriteStructLike = ` {{define "FieldFastWriteStructLike"}} + {{- if and (Features.WithFieldMask) .NeedFieldMask}} + {{- if Features.FieldMaskHalfway}} + {{.Target}}.Pass_FieldMask({{.FieldMask}}) + {{- else}} + {{.Target}}.Set_FieldMask({{.FieldMask}}) + {{- end}} + {{- end}} offset += {{.Target}}.FastWriteNocopy(buf[offset:], binaryWriter) {{- end}}{{/* define "FieldFastWriteStructLike" */}} ` const fieldStructLikeLength = ` {{define "FieldStructLikeLength"}} + {{- if and (Features.WithFieldMask) .NeedFieldMask}} + {{- if Features.FieldMaskHalfway}} + {{.Target}}.Pass_FieldMask({{.FieldMask}}) + {{- else}} + {{.Target}}.Set_FieldMask({{.FieldMask}}) + {{- end}} + {{- end}} l += {{.Target}}.BLength() {{- end}}{{/* define "FieldStructLikeLength" */}} ` @@ -695,9 +839,9 @@ const fieldFastWriteBaseType = ` {{- if .Type.Category.IsBinary}}{{$Value = printf "[]byte(%s)" $Value}}{{end}} {{- if IsBinaryOrStringType .Type}} offset += bthrift.Binary.Write{{.TypeID}}Nocopy(buf[offset:], binaryWriter, {{$Value}}) -{{else}} +{{- else}} offset += bthrift.Binary.Write{{.TypeID}}(buf[offset:], {{$Value}}) -{{end}} +{{- end}} {{- end}}{{/* define "FieldFastWriteBaseType" */}} ` @@ -709,9 +853,9 @@ const fieldBaseTypeLength = ` {{- if .Type.Category.IsBinary}}{{$Value = printf "[]byte(%s)" $Value}}{{end}} {{- if IsBinaryOrStringType .Type}} l += bthrift.Binary.{{.TypeID}}LengthNocopy({{$Value}}) -{{else}} +{{- else}} l += bthrift.Binary.{{.TypeID}}Length({{$Value}}) -{{end}} +{{- end}} {{- end}}{{/* define "FieldBaseTypeLength" */}} ` @@ -748,17 +892,39 @@ const fieldContainerLength = ` const fieldFastWriteMap = ` {{define "FieldFastWriteMap"}} +{{- $isIntKey := .KeyCtx.Type | IsIntType -}} +{{- $isStrKey := .KeyCtx.Type | IsStrType -}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := "nfm"}} mapBeginOffset := offset offset += bthrift.Binary.MapBeginLength(thrift. {{- .KeyCtx.Type | GetTypeIDConstant -}} , thrift.{{- .ValCtx.Type | GetTypeIDConstant -}}, 0) var length int for k, v := range {{.Target}}{ + {{- if Features.WithFieldMask}} + {{- if $isIntKey}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(int(k)); !ex { + continue + } else { + {{- else if $isStrKey}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Str(string(k)); !ex { + continue + } else { + {{- else}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(0); !ex { + continue + } else { + {{- end}} + {{- end}}{{/* end Features.WithFieldMask */}} length++ - {{$ctx := .KeyCtx.WithTarget "k"}} + {{- $ctx := (.KeyCtx.WithTarget "k").WithFieldMask ""}} {{- template "FieldFastWrite" $ctx}} - {{$ctx := .ValCtx.WithTarget "v"}} + {{- $ctx := (.ValCtx.WithTarget "v").WithFieldMask $curFieldMask}} {{- template "FieldFastWrite" $ctx}} + {{- if and Features.WithFieldMask}} + } + {{- end}} } bthrift.Binary.WriteMapBegin(buf[mapBeginOffset:], thrift. {{- .KeyCtx.Type | GetTypeIDConstant -}} @@ -770,11 +936,15 @@ const fieldFastWriteMap = ` const fieldMapLength = ` {{define "FieldMapLength"}} +{{- $isIntKey := .KeyCtx.Type | IsIntType -}} +{{- $isStrKey := .KeyCtx.Type | IsStrType -}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := .FieldMask}} l += bthrift.Binary.MapBeginLength(thrift. {{- .KeyCtx.Type | GetTypeIDConstant -}} , thrift.{{- .ValCtx.Type | GetTypeIDConstant -}} , len({{.Target}})) - {{- if and (IsFixedLengthType .KeyCtx.Type) (IsFixedLengthType .ValCtx.Type)}} + {{- if and (not Features.WithFieldMask) (and (IsFixedLengthType .KeyCtx.Type) (IsFixedLengthType .ValCtx.Type))}} var tmpK {{.KeyCtx.TypeName}} var tmpV {{.ValCtx.TypeName}} l += ({{- $ctx := .KeyCtx.WithTarget "tmpK" -}} @@ -783,10 +953,29 @@ const fieldMapLength = ` {{- template "FieldFixedLengthTypeLength" $ctx}}) * len({{.Target}}) {{- else}} for k, v := range {{.Target}}{ - {{$ctx := .KeyCtx.WithTarget "k"}} + {{- if Features.WithFieldMask}} + {{- $curFieldMask = "nfm"}} + {{- if $isIntKey}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(int(k)); !ex { + continue + } else { + {{- else if $isStrKey}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Str(string(k)); !ex { + continue + } else { + {{- else}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(0); !ex { + continue + } else { + {{- end}} + {{- end}}{{/* end Features.WithFieldMask */}} + {{$ctx := (.KeyCtx.WithTarget "k").WithFieldMask ""}} {{- template "FieldLength" $ctx}} - {{$ctx := .ValCtx.WithTarget "v"}} + {{- $ctx := (.ValCtx.WithTarget "v").WithFieldMask $curFieldMask -}} {{- template "FieldLength" $ctx}} + {{- if and Features.WithFieldMask}} + } + {{- end}} } {{- end}}{{/* if */}} l += bthrift.Binary.MapEndLength() @@ -795,15 +984,26 @@ const fieldMapLength = ` const fieldFastWriteSet = ` {{define "FieldFastWriteSet"}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := .FieldMask}} setBeginOffset := offset offset += bthrift.Binary.SetBeginLength(thrift. {{- .ValCtx.Type | GetTypeIDConstant -}}, 0) {{template "ValidateSet" .}} var length int - for _, v := range {{.Target}} { + for {{if Features.WithFieldMask}}i{{else}}_{{end}}, v := range {{.Target}} { + {{- if Features.WithFieldMask}} + {{- $curFieldMask = "nfm"}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(i); !ex { + continue + } else { + {{- end}} length++ - {{- $ctx := .ValCtx.WithTarget "v"}} + {{- $ctx := (.ValCtx.WithTarget "v").WithFieldMask $curFieldMask -}} {{- template "FieldFastWrite" $ctx}} + {{- if Features.WithFieldMask}} + } + {{- end}} } bthrift.Binary.WriteSetBegin(buf[setBeginOffset:], thrift. {{- .ValCtx.Type | GetTypeIDConstant -}} @@ -814,18 +1014,29 @@ const fieldFastWriteSet = ` const fieldSetLength = ` {{define "FieldSetLength"}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := .FieldMask}} l += bthrift.Binary.SetBeginLength(thrift. {{- .ValCtx.Type | GetTypeIDConstant -}} , len({{.Target}})) {{template "ValidateSet" .}} - {{- if IsFixedLengthType .ValCtx.Type}} + {{- if and (not Features.WithFieldMask) (IsFixedLengthType .ValCtx.Type)}} var tmpV {{.ValCtx.TypeName}} l += {{- $ctx := .ValCtx.WithTarget "tmpV" -}} {{- template "FieldFixedLengthTypeLength" $ctx -}} * len({{.Target}}) {{- else}} - for _, v := range {{.Target}} { - {{- $ctx := .ValCtx.WithTarget "v"}} + for {{if Features.WithFieldMask}}i{{else}}_{{end}}, v := range {{.Target}} { + {{- if Features.WithFieldMask}} + {{- $curFieldMask = "nfm"}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(i); !ex { + continue + } else { + {{- end}} + {{- $ctx := (.ValCtx.WithTarget "v").WithFieldMask $curFieldMask -}} {{- template "FieldLength" $ctx}} + {{- if Features.WithFieldMask}} + } + {{- end}} } {{- end}}{{/* if */}} l += bthrift.Binary.SetEndLength() @@ -834,14 +1045,25 @@ const fieldSetLength = ` const fieldFastWriteList = ` {{define "FieldFastWriteList"}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := .FieldMask}} listBeginOffset := offset offset += bthrift.Binary.ListBeginLength(thrift. {{- .ValCtx.Type | GetTypeIDConstant -}}, 0) var length int - for _, v := range {{.Target}} { + for {{if Features.WithFieldMask}}i{{else}}_{{end}}, v := range {{.Target}} { + {{- if Features.WithFieldMask}} + {{- $curFieldMask = "nfm"}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(i); !ex { + continue + } else { + {{- end}} length++ - {{- $ctx := .ValCtx.WithTarget "v"}} + {{- $ctx := (.ValCtx.WithTarget "v").WithFieldMask $curFieldMask -}} {{- template "FieldFastWrite" $ctx}} + {{- if Features.WithFieldMask}} + } + {{- end}} } bthrift.Binary.WriteListBegin(buf[listBeginOffset:], thrift. {{- .ValCtx.Type | GetTypeIDConstant -}} @@ -852,17 +1074,28 @@ const fieldFastWriteList = ` const fieldListLength = ` {{define "FieldListLength"}} +{{- $isBaseVal := .ValCtx.Type | IsBaseType -}} +{{- $curFieldMask := .FieldMask}} l += bthrift.Binary.ListBeginLength(thrift. {{- .ValCtx.Type | GetTypeIDConstant -}} , len({{.Target}})) - {{- if IsFixedLengthType .ValCtx.Type}} + {{- if and (not Features.WithFieldMask) (IsFixedLengthType .ValCtx.Type)}} var tmpV {{.ValCtx.TypeName}} l += {{- $ctx := .ValCtx.WithTarget "tmpV" -}} {{- template "FieldFixedLengthTypeLength" $ctx -}} * len({{.Target}}) {{- else}} - for _, v := range {{.Target}} { - {{- $ctx := .ValCtx.WithTarget "v"}} + for {{if Features.WithFieldMask}}i{{else}}_{{end}}, v := range {{.Target}} { + {{- if Features.WithFieldMask}} + {{- $curFieldMask = "nfm"}} + if {{if $isBaseVal}}_{{else}}{{$curFieldMask}}{{end}}, ex := {{.FieldMask}}.Int(i); !ex { + continue + } else { + {{- end}} + {{- $ctx := (.ValCtx.WithTarget "v").WithFieldMask $curFieldMask -}} {{- template "FieldLength" $ctx}} + {{- if Features.WithFieldMask}} + } + {{- end}} } {{- end}}{{/* if */}} l += bthrift.Binary.ListEndLength() @@ -873,10 +1106,14 @@ const processor = ` {{define "Processor"}} {{- range .Functions}} {{$ArgsType := .ArgType}} +{{- $withFieldMask := (SetWithFieldMask false) }} {{template "StructLikeCodec" $ArgsType}} +{{- $_ := (SetWithFieldMask $withFieldMask) }} {{- if not .Oneway}} {{$ResType := .ResType}} + {{- $withFieldMask := (SetWithFieldMask false) }} {{template "StructLikeCodec" $ResType}} + {{- $_ := (SetWithFieldMask $withFieldMask) }} {{- end}} {{- end}}{{/* range .Functions */}} {{- end}}{{/* define "Processor" */}} diff --git a/tool/internal_pkg/util/util.go b/tool/internal_pkg/util/util.go index b0677640b4..91af8ac189 100644 --- a/tool/internal_pkg/util/util.go +++ b/tool/internal_pkg/util/util.go @@ -26,6 +26,7 @@ import ( "path/filepath" "regexp" "runtime" + "sort" "strings" "unicode" @@ -278,3 +279,63 @@ func DownloadFile(remotePath, localPath string) error { func IDLName(filename string) string { return filepath.Base(filename) } + +type Import struct { + Alias string + Path string +} + +func SortImports(imps map[string]string, localPrefix string) (ret []Import) { + stds := make([]Import, 0, len(imps)) + locals := make([]Import, 0, len(imps)) + thirds := make([]Import, 0, len(imps)) + for path, alias := range imps { + if strings.HasPrefix(path, localPrefix+"/") { + locals = append(locals, Import{alias, path}) + } else if !strings.Contains(path, ".") { + stds = append(stds, Import{alias, path}) + } else { + thirds = append(thirds, Import{alias, path}) + } + } + + sort.SliceStable(stds, func(i, j int) bool { + return stds[i].Path < stds[j].Path + }) + ret = append(ret, stds...) + if len(thirds) > 0 { + ret = append(ret, Import{"", ""}) + } + sort.SliceStable(thirds, func(i, j int) bool { + return thirds[i].Path < thirds[j].Path + }) + ret = append(ret, thirds...) + if len(locals) > 0 { + ret = append(ret, Import{"", ""}) + } + sort.SliceStable(locals, func(i, j int) bool { + return locals[i].Path < locals[j].Path + }) + ret = append(ret, locals...) + return ret +} + +func (i Import) PackageName() string { + if i.Alias != "" { + return i.Alias + } else { + return strings.ToLower(filepath.Base(i.Path)) + } +} + +func PrintlImports(imports []Import) string { + builder := strings.Builder{} + for _, v := range imports { + if v.Path != "" { + builder.WriteString(fmt.Sprintf("%s %q\n", v.Alias, v.Path)) + } else { + builder.WriteString("\n") + } + } + return builder.String() +} From 7debf5c42bca261129e5ee01acece9f2af45f3ee Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Sat, 27 Apr 2024 14:50:03 +0800 Subject: [PATCH 22/49] fix failed tests --- server/server_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/server_test.go b/server/server_test.go index 21b4516da4..3013aa7a0e 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -437,7 +437,7 @@ func TestServiceRegistryInfoWithNilTags(t *testing.T) { } func TestServiceRegistryInfoWithSkipListenAddr(t *testing.T) { - realAddr := utils.NewNetAddr("tcp", test.GetLocalAddress()) + realAddr, _ := net.ResolveTCPAddr("tcp", test.GetLocalAddress()) registryInfo := ®istry.Info{ Weight: 100, Addr: realAddr, @@ -469,7 +469,7 @@ func TestServiceRegistryInfoWithSkipListenAddr(t *testing.T) { err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) test.Assert(t, err == nil) - time.AfterFunc(time.Second, func() { + time.AfterFunc(1500*time.Millisecond, func() { err := svr.Stop() test.Assert(t, err == nil, err) }) @@ -480,7 +480,7 @@ func TestServiceRegistryInfoWithSkipListenAddr(t *testing.T) { } func TestServiceRegistryInfoWithoutSkipListenAddr(t *testing.T) { - realAddr := utils.NewNetAddr("tcp", test.GetLocalAddress()) + realAddr, _ := net.ResolveTCPAddr("tcp", test.GetLocalAddress()) registryInfo := ®istry.Info{ Weight: 100, Addr: realAddr, @@ -511,7 +511,7 @@ func TestServiceRegistryInfoWithoutSkipListenAddr(t *testing.T) { err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) test.Assert(t, err == nil) - time.AfterFunc(time.Second, func() { + time.AfterFunc(1500*time.Millisecond, func() { err := svr.Stop() test.Assert(t, err == nil, err) }) From 505ce4f67ac98f8198309850ce3361cbd0425457 Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Thu, 2 May 2024 21:05:16 +0800 Subject: [PATCH 23/49] format code --- tool/internal_pkg/pluginmode/thriftgo/ast.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tool/internal_pkg/pluginmode/thriftgo/ast.go b/tool/internal_pkg/pluginmode/thriftgo/ast.go index 624e5c3280..c6f3779ee5 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/ast.go +++ b/tool/internal_pkg/pluginmode/thriftgo/ast.go @@ -19,11 +19,11 @@ import ( "github.com/cloudwego/thriftgo/parser" ) -func offsetTPL(assign string, offset string) string { +func offsetTPL(assign, offset string) string { return offset + " += " + assign + "\n" } -func ZeroWriter(t *parser.Type, oprot string, buf string, offset string) string { +func ZeroWriter(t *parser.Type, oprot, buf, offset string) string { switch t.GetCategory() { case parser.Category_Bool: return offsetTPL(oprot+".WriteBool("+buf+", false)", offset) @@ -58,7 +58,7 @@ func ZeroWriter(t *parser.Type, oprot string, buf string, offset string) string } } -func ZeroBLength(t *parser.Type, oprot string, offset string) string { +func ZeroBLength(t *parser.Type, oprot, offset string) string { switch t.GetCategory() { case parser.Category_Bool: return offsetTPL(oprot+".BoolLength(false)", offset) From 4af358471de8fad33acd29e2bc847fc8009e6856 Mon Sep 17 00:00:00 2001 From: XiaoYi-byte Date: Thu, 2 May 2024 21:14:00 +0800 Subject: [PATCH 24/49] format code --- tool/internal_pkg/pluginmode/thriftgo/patcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/internal_pkg/pluginmode/thriftgo/patcher.go b/tool/internal_pkg/pluginmode/thriftgo/patcher.go index d89f017773..5ff961d591 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/patcher.go +++ b/tool/internal_pkg/pluginmode/thriftgo/patcher.go @@ -65,7 +65,7 @@ type patcher struct { libs map[string]string } -func (p *patcher) UseLib(path string, alias string) string { +func (p *patcher) UseLib(path, alias string) string { if p.libs == nil { p.libs = make(map[string]string) } @@ -383,7 +383,7 @@ func getBashPath() string { } func (p *patcher) extractLocalLibs(imports []util.Import) []util.Import { - var ret = make([]util.Import, 0) + ret := make([]util.Import, 0) prefix := p.module + "/" // remove std libs and thrift to prevent duplicate import. for _, v := range imports { From 93f6c29c6cf591cc21b5e2437b5d761b92b8781a Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Mon, 6 May 2024 13:26:56 +0800 Subject: [PATCH 25/49] chore: update sonic/loader to v0.1.1 (#1342) --- go.mod | 6 +++--- go.sum | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index ce27c3cf0f..48f3b7f285 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/apache/thrift v0.13.0 github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b github.com/bytedance/mockey v1.2.7 - github.com/bytedance/sonic v1.11.5 + github.com/bytedance/sonic v1.11.6 github.com/choleraehyq/pid v0.0.18 - github.com/cloudwego/configmanager v0.2.1 - github.com/cloudwego/dynamicgo v0.2.3 + github.com/cloudwego/configmanager v0.2.2 + github.com/cloudwego/dynamicgo v0.2.4 github.com/cloudwego/fastpb v0.0.4 github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 diff --git a/go.sum b/go.sum index 815be21baf..17e5283f0b 100644 --- a/go.sum +++ b/go.sum @@ -17,10 +17,12 @@ github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b h1:R6PWoQtxEMpWJPH github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/mockey v1.2.7 h1:8j4yCqS5OmMe2dQCxPit4FVkwTK9nrykIgbOZN3s28o= github.com/bytedance/mockey v1.2.7/go.mod h1:bNrUnI1u7+pAc0TYDgPATM+wF2yzHxmNH+iDXg4AOCU= -github.com/bytedance/sonic v1.11.5 h1:G00FYjjqll5iQ1PYXynbg/hyzqBqavH8Mo9/oTopd9k= github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw= -github.com/bytedance/sonic/loader v0.1.0 h1:skjHJ2Bi9ibbq3Dwzh1w42MQ7wZJrXmEZr/uqUn3f0Q= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/choleraehyq/pid v0.0.18 h1:O7LLxPoOyt3YtonlCC8BmNrF9P6Hc8B509UOqlPSVhw= github.com/choleraehyq/pid v0.0.18/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= @@ -28,14 +30,16 @@ github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4M github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.3 h1:b5J/l8xolB7dyDTTmhJP2oTs5LdrjyrUFuNxdfq5hAg= github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1umPV+aN8pi8= -github.com/cloudwego/configmanager v0.2.1 h1:JRmM9HPl8IafjrUU5waNboYjhiF3H7HY5oKuyoiiZto= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/configmanager v0.2.1/go.mod h1:0oD/BWaTuznBOawVeTmTl3LE99RWaw7rX2jECowmY58= +github.com/cloudwego/configmanager v0.2.2 h1:sVrJB8gWYTlPV2OS3wcgJSO9F2/9Zbkmcm1Z7jempOU= +github.com/cloudwego/configmanager v0.2.2/go.mod h1:ppiyU+5TPLonE8qMVi/pFQk2eL3Q4P7d4hbiNJn6jwI= github.com/cloudwego/dynamicgo v0.2.2-dep3/go.mod h1:oFLzd9SEUtU7XbSc7AT9e5xoAV1OJ1mVpudtUOiD7PQ= github.com/cloudwego/dynamicgo v0.2.2/go.mod h1:k840iCFH9ng9PBqr6jIoOyZxdk58EPEccrbfOk4ni1s= -github.com/cloudwego/dynamicgo v0.2.3 h1:y+W/ISuVW8uDFZ94NGOMMPB1SWaRJ0o+8qKx/D1kMic= -github.com/cloudwego/dynamicgo v0.2.3/go.mod h1:fvksgIX2piXJ6cmLxmAfOYDf06lZh8Nfn06ulqJPDsw= +github.com/cloudwego/dynamicgo v0.2.4 h1:xdBezj2ziiKSir0+vdvFMWi8AESKdA1JPIuuB12LCU4= +github.com/cloudwego/dynamicgo v0.2.4/go.mod h1:BXXaLtNH/nNIZi5HsE8lupiMKPmTogJ8z+KGFEySqUg= github.com/cloudwego/fastpb v0.0.4 h1:/ROVVfoFtpfc+1pkQLzGs+azjxUbSOsAqSY4tAAx4mg= github.com/cloudwego/fastpb v0.0.4/go.mod h1:/V13XFTq2TUkxj2qWReV8MwfPC4NnPcy6FsrojnsSG0= github.com/cloudwego/frugal v0.1.15 h1:LC55UJKhQPMFVjDPbE+LJcF7etZjSx6uokG1tk0wPK0= From 43aad353a849cc353ad3d59fb2c8026a47de1ea6 Mon Sep 17 00:00:00 2001 From: Xuran <37136584+Duslia@users.noreply.github.com> Date: Tue, 7 May 2024 19:39:21 +0800 Subject: [PATCH 26/49] feat: export tpl (#1344) --- .../pluginmode/thriftgo/file_tpl.go | 20 +-- .../pluginmode/thriftgo/patcher.go | 118 +++++++++--------- .../pluginmode/thriftgo/register_tpl.go | 6 +- .../pluginmode/thriftgo/struct_tpl.go | 80 ++++++------ 4 files changed, 112 insertions(+), 112 deletions(-) diff --git a/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go b/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go index ce94dd136a..0552bd5cd5 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go +++ b/tool/internal_pkg/pluginmode/thriftgo/file_tpl.go @@ -14,13 +14,13 @@ package thriftgo -const file = ` +const File = ` {{define "file"}} // Code generated by Kitex {{Version}}. DO NOT EDIT. package {{.PkgName}} -` + importInsertPoint + ` +` + ImportInsertPoint + ` {{InsertionPoint "KitexUnusedProtection"}} // unused protection @@ -47,7 +47,7 @@ var ( {{end}}{{/* define "file" */}} ` -const imports = ` +const Imports = ` {{define "imports"}} import ( {{PrintImports .Imports}} @@ -63,7 +63,7 @@ var ( {{end}}{{/* define "imports" */}} ` -const body = ` +const Body = ` {{define "body"}} {{- range .Scope.StructLikes}} @@ -80,7 +80,7 @@ const body = ` {{- end}}{{/* define "body" */}} ` -const patchArgsAndResult = ` +const PatchArgsAndResult = ` {{define "ArgsAndResult"}} {{range $svc := .Scope.Services}} {{range .Functions}} @@ -115,9 +115,9 @@ func (p *{{$resType.GoName}}) GetResult() interface{} { ` var basicTemplates = []string{ - patchArgsAndResult, - file, - imports, - body, - registerHessian, + PatchArgsAndResult, + File, + Imports, + Body, + RegisterHessian, } diff --git a/tool/internal_pkg/pluginmode/thriftgo/patcher.go b/tool/internal_pkg/pluginmode/thriftgo/patcher.go index 5ff961d591..5bb80741ff 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/patcher.go +++ b/tool/internal_pkg/pluginmode/thriftgo/patcher.go @@ -122,66 +122,66 @@ func (p *patcher) buildTemplates() (err error) { templates.StructLikeDefault, templates.FieldGetOrSet, templates.FieldIsSet, - structLikeDeepCopy, - fieldDeepCopy, - fieldDeepCopyStructLike, - fieldDeepCopyContainer, - fieldDeepCopyMap, - fieldDeepCopyList, - fieldDeepCopySet, - fieldDeepCopyBaseType, - structLikeCodec, - structLikeProtocol, - javaClassName, - processor, + StructLikeDeepCopy, + FieldDeepCopy, + FieldDeepCopyStructLike, + FieldDeepCopyContainer, + FieldDeepCopyMap, + FieldDeepCopyList, + FieldDeepCopySet, + FieldDeepCopyBaseType, + StructLikeCodec, + StructLikeProtocol, + JavaClassName, + Processor, ) } else { - allTemplates = append(allTemplates, structLikeCodec, - structLikeFastRead, - structLikeFastReadField, - structLikeDeepCopy, - structLikeFastWrite, - structLikeFastWriteNocopy, - structLikeLength, - structLikeFastWriteField, - structLikeFieldLength, - structLikeProtocol, - javaClassName, - fieldFastRead, - fieldFastReadStructLike, - fieldFastReadBaseType, - fieldFastReadContainer, - fieldFastReadMap, - fieldFastReadSet, - fieldFastReadList, - fieldDeepCopy, - fieldDeepCopyStructLike, - fieldDeepCopyContainer, - fieldDeepCopyMap, - fieldDeepCopyList, - fieldDeepCopySet, - fieldDeepCopyBaseType, - fieldFastWrite, - fieldLength, - fieldFastWriteStructLike, - fieldStructLikeLength, - fieldFastWriteBaseType, - fieldBaseTypeLength, - fieldFixedLengthTypeLength, - fieldFastWriteContainer, - fieldContainerLength, - fieldFastWriteMap, - fieldMapLength, - fieldFastWriteSet, - fieldSetLength, - fieldFastWriteList, - fieldListLength, + allTemplates = append(allTemplates, StructLikeCodec, + StructLikeFastReadField, + StructLikeDeepCopy, + StructLikeFastWrite, + StructLikeFastRead, + StructLikeFastWriteNocopy, + StructLikeLength, + StructLikeFastWriteField, + StructLikeFieldLength, + StructLikeProtocol, + JavaClassName, + FieldFastRead, + FieldFastReadStructLike, + FieldFastReadBaseType, + FieldFastReadContainer, + FieldFastReadMap, + FieldFastReadSet, + FieldFastReadList, + FieldDeepCopy, + FieldDeepCopyStructLike, + FieldDeepCopyContainer, + FieldDeepCopyMap, + FieldDeepCopyList, + FieldDeepCopySet, + FieldDeepCopyBaseType, + FieldFastWrite, + FieldLength, + FieldFastWriteStructLike, + FieldStructLikeLength, + FieldFastWriteBaseType, + FieldBaseTypeLength, + FieldFixedLengthTypeLength, + FieldFastWriteContainer, + FieldContainerLength, + FieldFastWriteMap, + FieldMapLength, + FieldFastWriteSet, + FieldSetLength, + FieldFastWriteList, + FieldListLength, templates.FieldDeepEqual, templates.FieldDeepEqualBase, templates.FieldDeepEqualStructLike, templates.FieldDeepEqualContainer, - validateSet, - processor, + ValidateSet, + Processor, ) } for i, txt := range allTemplates { @@ -209,9 +209,9 @@ func (p *patcher) buildTemplates() (err error) { } if p.IsHessian2() { - tpl, err = tpl.Parse(registerHessian) + tpl, err = tpl.Parse(RegisterHessian) if err != nil { - return fmt.Errorf("failed to parse hessian2 templates: %w: %q", err, registerHessian) + return fmt.Errorf("failed to parse hessian2 templates: %w: %q", err, RegisterHessian) } } @@ -219,7 +219,7 @@ func (p *patcher) buildTemplates() (err error) { return nil } -const importInsertPoint = "// imports insert-point" +const ImportInsertPoint = "// imports insert-point" func (p *patcher) patch(req *plugin.Request) (patches []*plugin.Generated, err error) { if err := p.buildTemplates(); err != nil { @@ -311,8 +311,8 @@ func (p *patcher) patch(req *plugin.Request) (patches []*plugin.Generated, err e return nil, fmt.Errorf("%q: %w", ast.Filename, err) } imports := buf.String() - if i := strings.Index(content, importInsertPoint); i >= 0 { - content = strings.Replace(content, importInsertPoint, imports, 1) + if i := strings.Index(content, ImportInsertPoint); i >= 0 { + content = strings.Replace(content, ImportInsertPoint, imports, 1) } else { return nil, fmt.Errorf("replace imports failed") } diff --git a/tool/internal_pkg/pluginmode/thriftgo/register_tpl.go b/tool/internal_pkg/pluginmode/thriftgo/register_tpl.go index ae1333d0dc..064e8786fa 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/register_tpl.go +++ b/tool/internal_pkg/pluginmode/thriftgo/register_tpl.go @@ -14,7 +14,7 @@ package thriftgo -const registerHessian = ` +const RegisterHessian = ` {{- define "register"}} package {{ .PkgName}} @@ -136,7 +136,7 @@ func (p *{{$resType.GoName}}) Decode(d codec.Decoder) error { {{- end}}{{/* define RegisterHessian*/}} ` -const structLikeProtocol = ` +const StructLikeProtocol = ` {{define "StructLikeProtocol"}} {{- $TypeName := .GoName}} func (p *{{$TypeName}}) Encode(e codec.Encoder) error { @@ -180,7 +180,7 @@ func (p *{{$TypeName}}) Decode(d codec.Decoder) error { {{- end}}{{/* define "StructLikeProtocol" */}} ` -const javaClassName = ` +const JavaClassName = ` {{define "JavaClassName"}} {{- $TypeName := .GoName}} {{- $anno := .Annotations }} diff --git a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go index 8a23b8612b..4be29292a1 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go +++ b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go @@ -14,7 +14,7 @@ package thriftgo -const structLikeCodec = ` +const StructLikeCodec = ` {{define "StructLikeCodec"}} {{if GenerateFastAPIs}} {{template "StructLikeFastRead" .}} @@ -38,7 +38,7 @@ const structLikeCodec = ` {{- end}}{{/* define "StructLikeCodec" */}} ` -const structLikeFastRead = ` +const StructLikeFastRead = ` {{define "StructLikeFastRead"}} {{- $TypeName := .GoName}} func (p *{{$TypeName}}) FastRead(buf []byte) (int, error) { @@ -166,7 +166,7 @@ RequiredFieldNotSetError: {{- end}}{{/* define "StructLikeFastRead" */}} ` -const structLikeFastReadField = ` +const StructLikeFastReadField = ` {{define "StructLikeFastReadField"}} {{- $TypeName := .GoName}} {{- range .Fields}} @@ -199,7 +199,7 @@ func (p *{{$TypeName}}) FastReadField{{Str .ID}}(buf []byte) (int, error) { ` // TODO: check required -const structLikeDeepCopy = ` +const StructLikeDeepCopy = ` {{define "StructLikeDeepCopy"}} {{- $TypeName := .GoName}} func (p *{{$TypeName}}) DeepCopy(s interface{}) error { @@ -219,7 +219,7 @@ func (p *{{$TypeName}}) DeepCopy(s interface{}) error { {{- end}}{{/* define "StructLikeDeepCopy" */}} ` -const structLikeFastWrite = ` +const StructLikeFastWrite = ` {{define "StructLikeFastWrite"}} {{- $TypeName := .GoName}} // for compatibility @@ -229,7 +229,7 @@ func (p *{{$TypeName}}) FastWrite(buf []byte) int { {{- end}}{{/* define "StructLikeFastWrite" */}} ` -const structLikeFastWriteNocopy = ` +const StructLikeFastWriteNocopy = ` {{define "StructLikeFastWriteNocopy"}} {{- $TypeName := .GoName}} func (p *{{$TypeName}}) FastWriteNocopy(buf []byte, binaryWriter bthrift.BinaryWriter) int { @@ -263,7 +263,7 @@ CountSetFieldsError: {{- end}}{{/* define "StructLikeFastWriteNocopy" */}} ` -const structLikeLength = ` +const StructLikeLength = ` {{define "StructLikeLength"}} {{- $TypeName := .GoName}} func (p *{{$TypeName}}) BLength() int { @@ -297,7 +297,7 @@ CountSetFieldsError: {{- end}}{{/* define "StructLikeLength" */}} ` -const structLikeFastWriteField = ` +const StructLikeFastWriteField = ` {{define "StructLikeFastWriteField"}} {{- $TypeName := .GoName}} {{- range .Fields}} @@ -342,7 +342,7 @@ func (p *{{$TypeName}}) fastWriteField{{Str .ID}}(buf []byte, binaryWriter bthri {{- end}}{{/* define "StructLikeFastWriteField" */}} ` -const structLikeFieldLength = ` +const StructLikeFieldLength = ` {{define "StructLikeFieldLength"}} {{- $TypeName := .GoName}} {{- range .Fields}} @@ -387,7 +387,7 @@ func (p *{{$TypeName}}) field{{Str .ID}}Length() int { {{- end}}{{/* define "StructLikeFieldLength" */}} ` -const fieldFastRead = ` +const FieldFastRead = ` {{define "FieldFastRead"}} {{- if .Type.Category.IsStructLike}} {{- template "FieldFastReadStructLike" .}} @@ -399,7 +399,7 @@ const fieldFastRead = ` {{- end}}{{/* define "FieldFastRead" */}} ` -const fieldFastReadStructLike = ` +const FieldFastReadStructLike = ` {{define "FieldFastReadStructLike"}} {{- if .NeedDecl}} {{- .Target}} := {{.TypeName.Deref.NewFunc}}() @@ -419,7 +419,7 @@ const fieldFastReadStructLike = ` {{- end}}{{/* define "FieldFastReadStructLike" */}} ` -const fieldFastReadBaseType = ` +const FieldFastReadBaseType = ` {{define "FieldFastReadBaseType"}} {{- $DiffType := or .Type.Category.IsEnum .Type.Category.IsBinary}} {{- if .NeedDecl}} @@ -447,7 +447,7 @@ const fieldFastReadBaseType = ` {{- end}}{{/* define "FieldFastReadBaseType" */}} ` -const fieldFastReadContainer = ` +const FieldFastReadContainer = ` {{define "FieldFastReadContainer"}} {{- if eq "Map" .TypeID}} {{- template "FieldFastReadMap" .}} @@ -459,7 +459,7 @@ const fieldFastReadContainer = ` {{- end}}{{/* define "FieldFastReadContainer" */}} ` -const fieldFastReadMap = ` +const FieldFastReadMap = ` {{define "FieldFastReadMap"}} {{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} {{- $isIntKey := .KeyCtx.Type | IsIntType -}} @@ -534,7 +534,7 @@ const fieldFastReadMap = ` {{- end}}{{/* define "FieldFastReadMap" */}} ` -const fieldFastReadSet = ` +const FieldFastReadSet = ` {{define "FieldFastReadSet"}} {{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} {{- $isBaseVal := .ValCtx.Type | IsBaseType -}} @@ -584,7 +584,7 @@ const fieldFastReadSet = ` {{- end}}{{/* define "FieldFastReadSet" */}} ` -const fieldFastReadList = ` +const FieldFastReadList = ` {{define "FieldFastReadList"}} {{- $isStructVal := .ValCtx.Type.Category.IsStructLike -}} {{- $isBaseVal := .ValCtx.Type | IsBaseType -}} @@ -634,7 +634,7 @@ const fieldFastReadList = ` {{- end}}{{/* define "FieldFastReadList" */}} ` -const fieldDeepCopy = ` +const FieldDeepCopy = ` {{define "FieldDeepCopy"}} {{- if .Type.Category.IsStructLike}} {{- template "FieldDeepCopyStructLike" .}} @@ -646,7 +646,7 @@ const fieldDeepCopy = ` {{- end}}{{/* define "FieldDeepCopy" */}} ` -const fieldDeepCopyStructLike = ` +const FieldDeepCopyStructLike = ` {{define "FieldDeepCopyStructLike"}} {{- $Src := SourceTarget .Target}} {{- if .NeedDecl}} @@ -664,7 +664,7 @@ const fieldDeepCopyStructLike = ` {{- end}}{{/* define "FieldDeepCopyStructLike" */}} ` -const fieldDeepCopyContainer = ` +const FieldDeepCopyContainer = ` {{define "FieldDeepCopyContainer"}} {{- if eq "Map" .TypeID}} {{- template "FieldDeepCopyMap" .}} @@ -676,7 +676,7 @@ const fieldDeepCopyContainer = ` {{- end}}{{/* define "FieldDeepCopyContainer" */}} ` -const fieldDeepCopyMap = ` +const FieldDeepCopyMap = ` {{define "FieldDeepCopyMap"}} {{- $Src := SourceTarget .Target}} {{- if .NeedDecl}}var {{.Target}} {{.TypeName}}{{- end}} @@ -701,7 +701,7 @@ const fieldDeepCopyMap = ` {{- end}}{{/* define "FieldDeepCopyMap" */}} ` -const fieldDeepCopyList = ` +const FieldDeepCopyList = ` {{define "FieldDeepCopyList"}} {{- $Src := SourceTarget .Target}} {{if .NeedDecl}}var {{.Target}} {{.TypeName}}{{end}} @@ -720,7 +720,7 @@ const fieldDeepCopyList = ` {{- end}}{{/* define "FieldDeepCopyList" */}} ` -const fieldDeepCopySet = ` +const FieldDeepCopySet = ` {{define "FieldDeepCopySet"}} {{- $Src := SourceTarget .Target}} {{if .NeedDecl}}var {{.Target}} {{.TypeName}}{{end}} @@ -739,7 +739,7 @@ const fieldDeepCopySet = ` {{- end}}{{/* define "FieldDeepCopySet" */}} ` -const fieldDeepCopyBaseType = ` +const FieldDeepCopyBaseType = ` {{define "FieldDeepCopyBaseType"}} {{- $Src := SourceTarget .Target}} {{- if .NeedDecl}} @@ -781,7 +781,7 @@ const fieldDeepCopyBaseType = ` {{- end}}{{/* define "FieldDeepCopyBaseType" */}} ` -const fieldFastWrite = ` +const FieldFastWrite = ` {{define "FieldFastWrite"}} {{- if .Type.Category.IsStructLike}} {{- template "FieldFastWriteStructLike" . -}} @@ -793,7 +793,7 @@ const fieldFastWrite = ` {{- end}}{{/* define "FieldFastWrite" */}} ` -const fieldLength = ` +const FieldLength = ` {{define "FieldLength"}} {{- if .Type.Category.IsStructLike}} {{- template "FieldStructLikeLength" . -}} @@ -805,7 +805,7 @@ const fieldLength = ` {{- end}}{{/* define "FieldLength" */}} ` -const fieldFastWriteStructLike = ` +const FieldFastWriteStructLike = ` {{define "FieldFastWriteStructLike"}} {{- if and (Features.WithFieldMask) .NeedFieldMask}} {{- if Features.FieldMaskHalfway}} @@ -818,7 +818,7 @@ const fieldFastWriteStructLike = ` {{- end}}{{/* define "FieldFastWriteStructLike" */}} ` -const fieldStructLikeLength = ` +const FieldStructLikeLength = ` {{define "FieldStructLikeLength"}} {{- if and (Features.WithFieldMask) .NeedFieldMask}} {{- if Features.FieldMaskHalfway}} @@ -831,7 +831,7 @@ const fieldStructLikeLength = ` {{- end}}{{/* define "FieldStructLikeLength" */}} ` -const fieldFastWriteBaseType = ` +const FieldFastWriteBaseType = ` {{define "FieldFastWriteBaseType"}} {{- $Value := .Target}} {{- if .IsPointer}}{{$Value = printf "*%s" $Value}}{{end}} @@ -845,7 +845,7 @@ const fieldFastWriteBaseType = ` {{- end}}{{/* define "FieldFastWriteBaseType" */}} ` -const fieldBaseTypeLength = ` +const FieldBaseTypeLength = ` {{define "FieldBaseTypeLength"}} {{- $Value := .Target}} {{- if .IsPointer}}{{$Value = printf "*%s" $Value}}{{end}} @@ -859,14 +859,14 @@ const fieldBaseTypeLength = ` {{- end}}{{/* define "FieldBaseTypeLength" */}} ` -const fieldFixedLengthTypeLength = ` +const FieldFixedLengthTypeLength = ` {{define "FieldFixedLengthTypeLength"}} {{- $Value := .Target -}} bthrift.Binary.{{.TypeID}}Length({{TypeIDToGoType .TypeID}}({{$Value}})) {{- end -}}{{/* define "FieldFixedLengthTypeLength" */}} ` -const fieldFastWriteContainer = ` +const FieldFastWriteContainer = ` {{define "FieldFastWriteContainer"}} {{- if eq "Map" .TypeID}} {{- template "FieldFastWriteMap" .}} @@ -878,7 +878,7 @@ const fieldFastWriteContainer = ` {{- end}}{{/* define "FieldFastWriteContainer" */}} ` -const fieldContainerLength = ` +const FieldContainerLength = ` {{define "FieldContainerLength"}} {{- if eq "Map" .TypeID}} {{- template "FieldMapLength" .}} @@ -890,7 +890,7 @@ const fieldContainerLength = ` {{- end}}{{/* define "FieldContainerLength" */}} ` -const fieldFastWriteMap = ` +const FieldFastWriteMap = ` {{define "FieldFastWriteMap"}} {{- $isIntKey := .KeyCtx.Type | IsIntType -}} {{- $isStrKey := .KeyCtx.Type | IsStrType -}} @@ -934,7 +934,7 @@ const fieldFastWriteMap = ` {{- end}}{{/* define "FieldFastWriteMap" */}} ` -const fieldMapLength = ` +const FieldMapLength = ` {{define "FieldMapLength"}} {{- $isIntKey := .KeyCtx.Type | IsIntType -}} {{- $isStrKey := .KeyCtx.Type | IsStrType -}} @@ -982,7 +982,7 @@ const fieldMapLength = ` {{- end}}{{/* define "FieldMapLength" */}} ` -const fieldFastWriteSet = ` +const FieldFastWriteSet = ` {{define "FieldFastWriteSet"}} {{- $isBaseVal := .ValCtx.Type | IsBaseType -}} {{- $curFieldMask := .FieldMask}} @@ -1012,7 +1012,7 @@ const fieldFastWriteSet = ` {{- end}}{{/* define "FieldFastWriteSet" */}} ` -const fieldSetLength = ` +const FieldSetLength = ` {{define "FieldSetLength"}} {{- $isBaseVal := .ValCtx.Type | IsBaseType -}} {{- $curFieldMask := .FieldMask}} @@ -1043,7 +1043,7 @@ const fieldSetLength = ` {{- end}}{{/* define "FieldSetLength" */}} ` -const fieldFastWriteList = ` +const FieldFastWriteList = ` {{define "FieldFastWriteList"}} {{- $isBaseVal := .ValCtx.Type | IsBaseType -}} {{- $curFieldMask := .FieldMask}} @@ -1072,7 +1072,7 @@ const fieldFastWriteList = ` {{- end}}{{/* define "FieldFastWriteList" */}} ` -const fieldListLength = ` +const FieldListLength = ` {{define "FieldListLength"}} {{- $isBaseVal := .ValCtx.Type | IsBaseType -}} {{- $curFieldMask := .FieldMask}} @@ -1102,7 +1102,7 @@ const fieldListLength = ` {{- end}}{{/* define "FieldListLength" */}} ` -const processor = ` +const Processor = ` {{define "Processor"}} {{- range .Functions}} {{$ArgsType := .ArgType}} @@ -1119,7 +1119,7 @@ const processor = ` {{- end}}{{/* define "Processor" */}} ` -const validateSet = ` +const ValidateSet = ` {{define "ValidateSet"}} {{- if Features.ValidateSet}} {{- $ctx := (.ValCtx.WithTarget "tgt").WithSource "src"}} From bbf1ddc9c1832615c369fe2a418f098bc71740fc Mon Sep 17 00:00:00 2001 From: Joway Date: Tue, 14 May 2024 20:16:12 +0800 Subject: [PATCH 27/49] chore: use runtimex to replace choleraehyq/pid (#1347) --- CREDITS | 3 +-- go.mod | 2 +- go.sum | 3 ++- pkg/utils/ring.go | 10 +++++----- pkg/utils/runtimex.go | 37 +++++++++++++++++++++++++++++++++++++ pkg/utils/runtimex_test.go | 30 ++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 pkg/utils/runtimex.go create mode 100644 pkg/utils/runtimex_test.go diff --git a/CREDITS b/CREDITS index 8b862b18fe..fa01335116 100644 --- a/CREDITS +++ b/CREDITS @@ -1,2 +1 @@ -github.com/stretchr/testify -github.com/choleraehyq/pid \ No newline at end of file +github.com/stretchr/testify \ No newline at end of file diff --git a/go.mod b/go.mod index 48f3b7f285..0ff78b779e 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,13 @@ require ( github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b github.com/bytedance/mockey v1.2.7 github.com/bytedance/sonic v1.11.6 - github.com/choleraehyq/pid v0.0.18 github.com/cloudwego/configmanager v0.2.2 github.com/cloudwego/dynamicgo v0.2.4 github.com/cloudwego/fastpb v0.0.4 github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 github.com/cloudwego/netpoll v0.6.0 + github.com/cloudwego/runtimex v0.1.0 github.com/cloudwego/thriftgo v0.3.6 github.com/golang/mock v1.6.0 github.com/google/pprof v0.0.0-20220608213341-c488b8fa1db3 diff --git a/go.sum b/go.sum index 17e5283f0b..c4a8047d3f 100644 --- a/go.sum +++ b/go.sum @@ -24,7 +24,6 @@ github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9 github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/choleraehyq/pid v0.0.18 h1:O7LLxPoOyt3YtonlCC8BmNrF9P6Hc8B509UOqlPSVhw= github.com/choleraehyq/pid v0.0.18/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= @@ -53,6 +52,8 @@ github.com/cloudwego/localsession v0.0.2 h1:N9/IDtCPj1fCL9bCTP+DbXx3f40YjVYWcwkJ github.com/cloudwego/localsession v0.0.2/go.mod h1:kiJxmvAcy4PLgKtEnPS5AXed3xCiXcs7Z+KBHP72Wv8= github.com/cloudwego/netpoll v0.6.0 h1:JRMkrA1o8k/4quxzg6Q1XM+zIhwZsyoWlq6ef+ht31U= github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= +github.com/cloudwego/runtimex v0.1.0 h1:HG+WxWoj5/CDChDZ7D99ROwvSMkuNXAqt6hnhTTZDiI= +github.com/cloudwego/runtimex v0.1.0/go.mod h1:23vL/HGV0W8nSCHbe084AgEBdDV4rvXenEUMnUNvUd8= github.com/cloudwego/thriftgo v0.2.11/go.mod h1:dAyXHEmKXo0LfMCrblVEY3mUZsdeuA5+i0vF5f09j7E= github.com/cloudwego/thriftgo v0.3.6 h1:gHHW8Ag3cAEQ/awP4emTJiRPr5yQjbANhcsmV8/Epbw= github.com/cloudwego/thriftgo v0.3.6/go.mod h1:29ukiySoAMd0vXMYIduAY9dph/7dmChvOS11YLotFb8= diff --git a/pkg/utils/ring.go b/pkg/utils/ring.go index 71e4de2339..16f644dbb2 100644 --- a/pkg/utils/ring.go +++ b/pkg/utils/ring.go @@ -19,13 +19,12 @@ package utils import ( "errors" "runtime" - - goid "github.com/choleraehyq/pid" ) // ErrRingFull means the ring is full. var ErrRingFull = errors.New("ring is full") +// Deprecated: it's not used by kitex anymore. // NewRing creates a ringbuffer with fixed size. func NewRing(size int) *Ring { if size <= 0 { @@ -52,6 +51,7 @@ func NewRing(size int) *Ring { return r } +// Deprecated: it's not used by kitex anymore. // Ring implements a fixed size hash list to manage data type Ring struct { length int @@ -64,7 +64,7 @@ func (r *Ring) Push(obj interface{}) error { return r.rings[0].Push(obj) } - idx := goid.GetPid() % r.length + idx := getGoroutineID() % r.length for i := 0; i < r.length; i, idx = i+1, (idx+1)%r.length { err := r.rings[idx].Push(obj) if err == nil { @@ -80,7 +80,7 @@ func (r *Ring) Pop() interface{} { return r.rings[0].Pop() } - idx := goid.GetPid() % r.length + idx := getGoroutineID() % r.length for i := 0; i < r.length; i, idx = i+1, (idx+1)%r.length { obj := r.rings[idx].Pop() if obj != nil { @@ -94,7 +94,7 @@ func (r *Ring) Pop() interface{} { func (r *Ring) Dump() interface{} { m := &ringDump{} dumpList := make([]*ringDump, 0, r.length) - idx := goid.GetPid() % r.length + idx := getGoroutineID() % r.length for i := 0; i < r.length; i, idx = i+1, (idx+1)%r.length { curDump := &ringDump{} r.rings[idx].Dump(curDump) diff --git a/pkg/utils/runtimex.go b/pkg/utils/runtimex.go new file mode 100644 index 0000000000..5bbdecb641 --- /dev/null +++ b/pkg/utils/runtimex.go @@ -0,0 +1,37 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package utils + +import ( + "github.com/bytedance/gopkg/lang/fastrand" + "github.com/cloudwego/runtimex" +) + +// getGoroutineID return current G's ID, it will fall back to get a random int, +// the caller should make sure it's ok if it cannot get correct goroutine id. +func getGoroutineID() int { + gid, err := runtimex.GID() + if err == nil { + return gid + } + // We use a Unit Test to ensure kitex should compatible with new Go versions, so it will have a rare choice to meet the rand fallback + gid = fastrand.Int() + if gid < 0 { + gid = -gid + } + return gid +} diff --git a/pkg/utils/runtimex_test.go b/pkg/utils/runtimex_test.go new file mode 100644 index 0000000000..713a9b6527 --- /dev/null +++ b/pkg/utils/runtimex_test.go @@ -0,0 +1,30 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package utils + +import ( + "testing" + + "github.com/cloudwego/runtimex" +) + +func TestRuntimeXCompatibility(t *testing.T) { + _, err := runtimex.GID() + if err != nil { + t.Fatal("Your runtimex package is not compatible with current Go runtime version !!!!!!!!!") + } +} From 137b11f1e10241978ef707ca14e1b7327715c94f Mon Sep 17 00:00:00 2001 From: Marina Sakai <118230951+Marina-Sakai@users.noreply.github.com> Date: Wed, 15 May 2024 11:46:03 +0800 Subject: [PATCH 28/49] feat(generic): set dynamicgo parse mode (#1346) --- pkg/generic/json_test/generic_test.go | 19 +++++++ .../idl/example_multi_service.thrift | 56 +++++++++++++++++++ pkg/generic/thriftidl_provider.go | 3 +- pkg/generic/thriftidl_provider_test.go | 20 +++++++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 pkg/generic/json_test/idl/example_multi_service.thrift diff --git a/pkg/generic/json_test/generic_test.go b/pkg/generic/json_test/generic_test.go index 78b9562687..7d23cdd104 100644 --- a/pkg/generic/json_test/generic_test.go +++ b/pkg/generic/json_test/generic_test.go @@ -39,6 +39,7 @@ import ( "github.com/cloudwego/kitex/internal/test" "github.com/cloudwego/kitex/pkg/generic" "github.com/cloudwego/kitex/pkg/generic/descriptor" + "github.com/cloudwego/kitex/pkg/generic/thrift" "github.com/cloudwego/kitex/server" "github.com/cloudwego/kitex/transport" ) @@ -60,6 +61,7 @@ func TestRun(t *testing.T) { t.Run("TestThriftBase64BinaryEcho", testThriftBase64BinaryEcho) t.Run("TestRegression", testRegression) t.Run("TestJSONThriftGenericClientFinalizer", testJSONThriftGenericClientFinalizer) + t.Run("TestParseModeWithDynamicGo", testParseModeWithDynamicGo) } func testThrift(t *testing.T) { @@ -704,6 +706,23 @@ func testJSONThriftGenericClientFinalizer(t *testing.T) { test.Assert(t, thirddGCHeapAlloc < secondGCHeapAlloc/2 && thirdGCHeapObjects < secondGCHeapObjects/2) } +func testParseModeWithDynamicGo(t *testing.T) { + addr := test.GetLocalAddress() + thrift.SetDefaultParseMode(thrift.FirstServiceOnly) + svr := initThriftServer(t, addr, new(GenericServiceImpl), "./idl/example_multi_service.thrift", nil, nil, true) + + // write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16) + // read: dynamicgo + cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example_multi_service.thrift", nil, nil, true) + resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second)) + test.Assert(t, err == nil, err) + respStr, ok := resp.(string) + test.Assert(t, ok) + test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "Msg").String(), "world"), "world") + + svr.Stop() +} + func mb(byteSize uint64) float32 { return float32(byteSize) / float32(1024*1024) } diff --git a/pkg/generic/json_test/idl/example_multi_service.thrift b/pkg/generic/json_test/idl/example_multi_service.thrift new file mode 100644 index 0000000000..5b3d7ca741 --- /dev/null +++ b/pkg/generic/json_test/idl/example_multi_service.thrift @@ -0,0 +1,56 @@ +include "base.thrift" +include "self_ref.thrift" +include "extend.thrift" +namespace go kitex.test.server + +enum FOO { + A = 1; +} + +struct InnerBase { + 255: base.Base Base, +} + +struct ExampleReq { + 1: required string Msg, + 2: FOO Foo, + 3: InnerBase InnerBase, + 4: optional i8 I8, + 5: optional i16 I16, + 6: optional i32 I32, + 7: optional i64 I64, + 8: optional double Double, + 255: base.Base Base, +} +struct ExampleResp { + 1: required string Msg, + 2: string required_field, + 3: optional i64 num (api.js_conv="true"), + 4: optional i8 I8, + 5: optional i16 I16, + 6: optional i32 I32, + 7: optional i64 I64, + 8: optional double Double, + 255: base.BaseResp BaseResp, +} +exception Exception { + 1: i32 code + 2: string msg +} + +struct A { + 1: A self + 2: self_ref.A a +} + +service ExampleService extends extend.ExtendService { + ExampleResp ExampleMethod(1: ExampleReq req)throws(1: Exception err), + A Foo(1: A req) + string Ping(1: string msg) + oneway void Oneway(1: string msg) + void Void(1: string msg) +} + +service Example2Service { + ExampleResp Example2Method(1: ExampleReq req) +} \ No newline at end of file diff --git a/pkg/generic/thriftidl_provider.go b/pkg/generic/thriftidl_provider.go index 7a14524364..b43d54eb41 100644 --- a/pkg/generic/thriftidl_provider.go +++ b/pkg/generic/thriftidl_provider.go @@ -22,6 +22,7 @@ import ( "path/filepath" "sync" + "github.com/cloudwego/dynamicgo/meta" dthrift "github.com/cloudwego/dynamicgo/thrift" "github.com/cloudwego/thriftgo/parser" @@ -68,7 +69,7 @@ func NewThriftFileProviderWithDynamicGo(path string, includeDirs ...string) (Des } // ServiceDescriptor of dynamicgo - dOpts := dthrift.Options{EnableThriftBase: true} + dOpts := dthrift.Options{EnableThriftBase: true, ParseServiceMode: meta.ParseServiceMode(thrift.DefaultParseMode())} dsvc, err := dOpts.NewDescritorFromPath(context.Background(), path, includeDirs...) if err != nil { // fall back to the original way (without dynamicgo) diff --git a/pkg/generic/thriftidl_provider_test.go b/pkg/generic/thriftidl_provider_test.go index dd9ac9449b..c14074241f 100644 --- a/pkg/generic/thriftidl_provider_test.go +++ b/pkg/generic/thriftidl_provider_test.go @@ -23,6 +23,7 @@ import ( dthrift "github.com/cloudwego/dynamicgo/thrift" "github.com/cloudwego/kitex/internal/test" + "github.com/cloudwego/kitex/pkg/generic/thrift" ) func TestAbsPath(t *testing.T) { @@ -235,3 +236,22 @@ func TestDisableGoTagForDynamicGo(t *testing.T) { test.Assert(t, tree.DynamicGoDsc != nil) test.Assert(t, tree.DynamicGoDsc.Functions()["BinaryEcho"].Request().Struct().FieldByKey("req").Type().Struct().FieldById(4).Alias() == "str") } + +func TestParseMode(t *testing.T) { + path := "json_test/idl/example_multi_service.thrift" + p, err := NewThriftFileProviderWithDynamicGo(path) + test.Assert(t, err == nil) + defer p.Close() + tree := <-p.Provide() + test.Assert(t, tree != nil) + test.Assert(t, tree.Name == "Example2Service") + test.Assert(t, tree.DynamicGoDsc.Name() == "Example2Service") + + thrift.SetDefaultParseMode(thrift.FirstServiceOnly) + p, err = NewThriftFileProviderWithDynamicGo(path) + test.Assert(t, err == nil) + tree = <-p.Provide() + test.Assert(t, tree != nil) + test.Assert(t, tree.Name == "ExampleService") + test.Assert(t, tree.DynamicGoDsc.Name() == "ExampleService") +} From a74dfc289a3805d65f7345257d80743c1f2b6523 Mon Sep 17 00:00:00 2001 From: Scout Wang Date: Wed, 15 May 2024 11:51:24 +0800 Subject: [PATCH 29/49] feat(kitex tool): support dependencies compatibility checking (#1316) --- tool/cmd/kitex/args/args.go | 2 + tool/cmd/kitex/main.go | 17 + tool/cmd/kitex/versions/dependencies.go | 311 ++++++++++++++++ tool/cmd/kitex/versions/dependencies_test.go | 339 ++++++++++++++++++ tool/cmd/kitex/versions/version.go | 106 ++++++ tool/cmd/kitex/versions/version_test.go | 118 ++++++ tool/internal_pkg/generator/generator.go | 2 + tool/internal_pkg/generator/generator_test.go | 2 +- 8 files changed, 896 insertions(+), 1 deletion(-) create mode 100644 tool/cmd/kitex/versions/dependencies.go create mode 100644 tool/cmd/kitex/versions/dependencies_test.go create mode 100644 tool/cmd/kitex/versions/version.go create mode 100644 tool/cmd/kitex/versions/version_test.go diff --git a/tool/cmd/kitex/args/args.go b/tool/cmd/kitex/args/args.go index 3f04295571..96564a0432 100644 --- a/tool/cmd/kitex/args/args.go +++ b/tool/cmd/kitex/args/args.go @@ -125,6 +125,8 @@ func (a *Arguments) buildFlags(version string) *flag.FlagSet { "Generate codes with injecting deep copy method.") f.StringVar(&a.Protocol, "protocol", "", "Specify a protocol for codec") + f.BoolVar(&a.NoDependencyCheck, "no-dependency-check", false, + "Skip dependency checking.") a.RecordCmd = os.Args a.Version = version a.ThriftOptions = append(a.ThriftOptions, diff --git a/tool/cmd/kitex/main.go b/tool/cmd/kitex/main.go index 3a92aa00c4..a2585b29e4 100644 --- a/tool/cmd/kitex/main.go +++ b/tool/cmd/kitex/main.go @@ -24,6 +24,7 @@ import ( "github.com/cloudwego/kitex" kargs "github.com/cloudwego/kitex/tool/cmd/kitex/args" + "github.com/cloudwego/kitex/tool/cmd/kitex/versions" "github.com/cloudwego/kitex/tool/internal_pkg/log" "github.com/cloudwego/kitex/tool/internal_pkg/pluginmode/protoc" "github.com/cloudwego/kitex/tool/internal_pkg/pluginmode/thriftgo" @@ -45,6 +46,15 @@ func init() { } }, }) + if err := versions.RegisterMinDepVersion( + &versions.MinDepVersion{ + RefPath: "github.com/cloudwego/kitex", + Version: "v0.9.0", + }, + ); err != nil { + log.Warn(err) + os.Exit(versions.CompatibilityCheckExitCode) + } } func main() { @@ -62,6 +72,13 @@ func main() { // run as kitex args.ParseArgs(kitex.Version) + if !args.NoDependencyCheck { + // check dependency compatibility between kitex cmd tool and dependency in go.mod + if err := versions.DefaultCheckDependencyAndProcess(); err != nil { + os.Exit(versions.CompatibilityCheckExitCode) + } + } + out := new(bytes.Buffer) cmd := args.BuildCmd(out) err := cmd.Run() diff --git a/tool/cmd/kitex/versions/dependencies.go b/tool/cmd/kitex/versions/dependencies.go new file mode 100644 index 0000000000..c552b83dd5 --- /dev/null +++ b/tool/cmd/kitex/versions/dependencies.go @@ -0,0 +1,311 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package versions + +import ( + "context" + "errors" + "fmt" + "os/exec" + "regexp" + "strings" + "time" + + "github.com/cloudwego/kitex" + "github.com/cloudwego/kitex/tool/internal_pkg/log" +) + +const ( + CompatibilityCheckExitCode = 3 +) + +var dm *dependencyManager + +func init() { + dm = &dependencyManager{} +} + +// RegisterMinDepVersion registers Minimal Dependency Version to be checked +func RegisterMinDepVersion(depVer *MinDepVersion) error { + return dm.Register(depVer) +} + +// CheckDependency is responsible for checking the compatibility of dependency +// and return the checking results. +// After calling CheckDependencies(), you should check if the result is Nil: +// +// res := CheckDependency() +// if res != nil { +// } +// +// If res is not nil, pls check the related error with `Err()`: +// +// if err := res.Err(); err != nil { +// switch { +// case errors.Is(err, ErrGoCmdNotFound): +// case errors.Is(err, ErrGoModNotFound): +// case errors.Is(err, ErrDependencyNotFound): +// case errors.Is(err, ErrDependencyVersionNotSemantic): +// case errors.Is(err, ErrDependencyReplacedWithLocalRepo): +// case errors.Is(err, ErrDependencyVersionNotCompatible): +// } +// } +// +// Then you can get the version in go.mod with `GoModVersion()` and retrieve the MinDepVersion +// information with `MinDepVersion()`. +func CheckDependency() *CheckResult { + return dm.CheckDependency() +} + +// DefaultCheckDependencyAndProcess provided default processing procedure to parse +// CheckResult and prompt users +func DefaultCheckDependencyAndProcess() error { + cr := CheckDependency() + if cr == nil { + return nil + } + res, shouldExit := defaultParseCheckResult(cr) + if res != "" { + log.Warn(res) + } + if shouldExit { + return errors.New("kitex cmd tool dependency compatibility check failed") + } + return nil +} + +func defaultParseCheckResult(cr *CheckResult) (prompt string, shouldExit bool) { + if cr == nil { + return + } + if err := cr.Err(); err != nil && (errors.Is(err, ErrDependencyVersionNotCompatible)) { + prompt = defaultPromptWithCheckResult(cr) + shouldExit = true + } + + return +} + +var defaultPrompt = `# Kitex Cmd Tool %s is not compatible with %s %s in your go.mod +# You can upgrade %s to latest version +go get %s@latest +# Or upgrade %s to %s version +go get %s@%s +# Or downgrade Kitex Cmd Tool to %s version +go install github.com/cloudwego/kitex/tool/cmd/kitex@%s` + +func defaultPromptWithCheckResult(cr *CheckResult) string { + depVer := cr.MinDepVersion() + goModVer := cr.GoModVersion() + kitexName := "Kitex" + return fmt.Sprintf(defaultPrompt, kitex.Version, depVer.RefPath, goModVer, + kitexName, + depVer.RefPath, + kitexName, kitex.Version, + depVer.RefPath, kitex.Version, + goModVer, + goModVer, + ) +} + +type MinDepVersion struct { + // RefPath is the reference path to the dependency + // e.g. github.com/cloudwego/kitex + RefPath string + // Version is the minimal required version + Version string + + ver *version + goModVer *version +} + +func (m *MinDepVersion) init() error { + if m == nil { + return errors.New("nil MinDepVersion") + } + if m.RefPath == "" { + return errors.New("empty RefPath") + } + if m.Version == "" { + return errors.New("empty Version") + } + ver, err := newVersion(m.Version) + if err != nil { + return err + } + m.ver = ver + + return nil +} + +func (m *MinDepVersion) parseGoModVersion() error { + res, err := runGoListCmd(m.RefPath) + if err != nil { + return err + } + + verStr := parseGoListVersion(res) + // replace with local repository + if verStr == "" { + return ErrDependencyReplacedWithLocalRepo + } + + ver, err := newVersion(verStr) + if err != nil { + return ErrDependencyVersionNotSemantic + } + m.goModVer = ver + + return nil +} + +func (m *MinDepVersion) getGoModVersion() string { + return m.goModVer.String() +} + +func (m *MinDepVersion) isCompatible() bool { + return m.goModVer.greatOrEqual(m.ver) +} + +var ( + ErrGoCmdNotFound = errors.New("go cmd not found") + ErrGoModNotFound = errors.New("go.mod file not found in current directory or any parent directory") + ErrDependencyNotFound = errors.New("dependency not found") + ErrDependencyVersionNotCompatible = errors.New("dependency not compatible") + ErrDependencyVersionNotSemantic = errors.New("dependency version is not semantic version") + ErrDependencyReplacedWithLocalRepo = errors.New("dependency replaced with local repo") +) + +type CheckResult struct { + ver *MinDepVersion + goModVer string + err error +} + +func (cr *CheckResult) MinDepVersion() *MinDepVersion { + return cr.ver +} + +// Err returns the error in checking process +func (cr *CheckResult) Err() error { + return cr.err +} + +// GoModVersion returns the version of the dependency in the go.mod +func (cr *CheckResult) GoModVersion() string { + return cr.goModVer +} + +type dependencyManager struct { + depVer *MinDepVersion +} + +func (dm *dependencyManager) Register(depVer *MinDepVersion) error { + if err := depVer.init(); err != nil { + return err + } + dm.depVer = depVer + + return nil +} + +func (dm *dependencyManager) CheckDependency() *CheckResult { + if dm.depVer == nil { + return nil + } + err := dm.depVer.parseGoModVersion() + return generateCheckResult(dm.depVer, err) +} + +func runGoListCmd(refPath string) (string, error) { + res, err := runCommand("go list -m " + refPath) + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + return "", ErrGoCmdNotFound + } + if strings.Contains(res, "go.mod file not found") { + return "", ErrGoModNotFound + } + return "", ErrDependencyNotFound + } + return res, nil +} + +func runCommand(input string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + arr := strings.Split(input, " ") + cmd := exec.CommandContext(ctx, arr[0], arr[1:]...) + output, err := cmd.CombinedOutput() + // return output even if err is not nil + // it can help the invoker to determine the specific scenario + return strings.TrimSpace(string(output)), err +} + +var goListVersionRegexp = regexp.MustCompile(`^(\S+)\s+(\S+)(\s+=>\s+)?(\S+)?(\s+)?(\S+)?$`) + +// parseGoToolVersion parses the version string returned by `go list -m {dependency}` +// and returns the exact version of the dependency. +// e.g. +// +// verStr: "github.com/cloudwego/kitex v0.9.0" +// result: "v0.9.0" +// verStr: github.com/cloudwego/kitex v0.9.0 => github.com/cloudwego/kitex v0.9.1 +// result: "v0.9.1" +// verStr: "github.com/cloudwego/kitex v0.9.0 => ./kitex" +// result: "" +func parseGoListVersion(str string) string { + // e.g. github.com/cloudwego/kitex v0.9.0 => github.com/cloudwego/kitex v0.9.1 + // parts[0]: "github.com/cloudwego/kitex v0.9.0 => github.com/cloudwego/kitex v0.9.1" + // parts[1]: "github.com/cloudwego/kitex" + // parts[2]: "v0.9.0" + // parts[3]: " => " + // parts[4]: "github.com/cloudwego/kitex" + // parts[5]: " " + // parts[6]: "v0.9.1" + parts := goListVersionRegexp.FindStringSubmatch(str) + if parts == nil { + return "" + } + // verify whether verStr is with replacement format + if parts[3] == "" { + return parts[2] + } + // verify whether replacing with local repository + // e.g. github.com/cloudwego/kitex v0.9.0 => ./kitex + // parts[6]: "" + if parts[6] != "" { + return parts[6] + } + + return "" +} + +func generateCheckResult(depVer *MinDepVersion, err error) *CheckResult { + cr := &CheckResult{ + ver: depVer, + } + if err != nil { + cr.err = err + return cr + } + cr.goModVer = depVer.getGoModVersion() + + if !depVer.isCompatible() { + cr.err = ErrDependencyVersionNotCompatible + } + + return cr +} diff --git a/tool/cmd/kitex/versions/dependencies_test.go b/tool/cmd/kitex/versions/dependencies_test.go new file mode 100644 index 0000000000..09c99151b1 --- /dev/null +++ b/tool/cmd/kitex/versions/dependencies_test.go @@ -0,0 +1,339 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package versions + +import ( + "errors" + "fmt" + "os/exec" + "testing" + + "github.com/cloudwego/kitex" + + "github.com/cloudwego/kitex/internal/test" +) + +func TestRegisterMinDepVersion(t *testing.T) { + refPath := "github.com/cloudwego/kitex" + testcases := []struct { + desc string + depVer *MinDepVersion + expectErr bool + }{ + { + desc: "correct MinDepVersion", + depVer: &MinDepVersion{ + RefPath: refPath, + Version: "v0.9.0", + }, + }, + { + desc: "correct MinDepVersion with prerelease information", + depVer: &MinDepVersion{ + RefPath: refPath, + Version: "v0.9.0-rc1", + }, + }, + { + desc: "MinDepVersion missing RefPath", + depVer: &MinDepVersion{ + RefPath: "", + }, + expectErr: true, + }, + { + desc: "MinDepVersion missing Version", + depVer: &MinDepVersion{ + RefPath: refPath, + Version: "", + }, + expectErr: true, + }, + { + desc: "MinDepVersion with non-semantic MinimalVersion", + depVer: &MinDepVersion{ + RefPath: refPath, + Version: "0.9.0", + }, + expectErr: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + err := RegisterMinDepVersion(tc.depVer) + if tc.expectErr { + test.Assert(t, err != nil) + } else { + test.Assert(t, err == nil) + } + }) + } +} + +func TestCheckDependencies(t *testing.T) { + testcases := []struct { + desc string + depVer *MinDepVersion + expectFunc func(t *testing.T, cr *CheckResult) + }{ + { + desc: "Dependency is compatible", + depVer: &MinDepVersion{ + RefPath: "github.com/cloudwego/thriftgo", + Version: "v0.3.6", + }, + expectFunc: func(t *testing.T, cr *CheckResult) { + test.Assert(t, cr != nil) + test.Assert(t, cr.Err() == nil) + test.Assert(t, cr.GoModVersion() != "") + test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/thriftgo") + test.Assert(t, cr.MinDepVersion().Version == "v0.3.6") + }, + }, + { + desc: "Dependency is compatible with prerelease information", + depVer: &MinDepVersion{ + RefPath: "github.com/cloudwego/thriftgo", + Version: "v0.3.6-rc1", + }, + expectFunc: func(t *testing.T, cr *CheckResult) { + test.Assert(t, cr != nil) + test.Assert(t, cr.Err() == nil) + test.Assert(t, cr.GoModVersion() != "") + test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/thriftgo") + test.Assert(t, cr.MinDepVersion().Version == "v0.3.6-rc1") + }, + }, + { + desc: "Dependency is not compatible", + depVer: &MinDepVersion{ + RefPath: "github.com/cloudwego/thriftgo", + Version: "v999.999.999", + }, + expectFunc: func(t *testing.T, cr *CheckResult) { + test.Assert(t, cr != nil) + test.Assert(t, errors.Is(cr.Err(), ErrDependencyVersionNotCompatible)) + test.Assert(t, cr.GoModVersion() != "") + test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/thriftgo") + test.Assert(t, cr.MinDepVersion().Version == "v999.999.999") + }, + }, + { + desc: "Dependency does not exist", + depVer: &MinDepVersion{ + RefPath: "xxx/yyy/zzz", + Version: "v999.999.999", + }, + expectFunc: func(t *testing.T, cr *CheckResult) { + test.Assert(t, cr != nil) + test.Assert(t, errors.Is(cr.Err(), ErrDependencyNotFound)) + test.Assert(t, cr.GoModVersion() == "") + test.Assert(t, cr.MinDepVersion().RefPath == "xxx/yyy/zzz") + test.Assert(t, cr.MinDepVersion().Version == "v999.999.999") + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + defer func() { + err := recover() + test.Assert(t, err == nil, err) + }() + RegisterMinDepVersion(tc.depVer) + res := CheckDependency() + tc.expectFunc(t, res) + }) + } +} + +func Test_defaultParseCheckResult(t *testing.T) { + testcases := []struct { + desc string + cr *CheckResult + expect func(*testing.T, string, bool) + }{ + { + desc: "Kitex is incompatible", + cr: &CheckResult{ + ver: &MinDepVersion{ + RefPath: "github.com/cloudwego/kitex", + Version: "v0.9.0", + }, + goModVer: "v0.8.0", + err: ErrDependencyVersionNotCompatible, + }, + expect: func(t *testing.T, prompt string, shouldExit bool) { + test.Assert(t, shouldExit == true) + test.Assert(t, prompt == fmt.Sprintf(`# Kitex Cmd Tool %s is not compatible with github.com/cloudwego/kitex v0.8.0 in your go.mod +# You can upgrade Kitex to latest version +go get github.com/cloudwego/kitex@latest +# Or upgrade Kitex to %s version +go get github.com/cloudwego/kitex@%s +# Or downgrade Kitex Cmd Tool to v0.8.0 version +go install github.com/cloudwego/kitex/tool/cmd/kitex@v0.8.0`, kitex.Version, kitex.Version, kitex.Version)) + }, + }, + { + desc: "Kitex is compatible", + cr: &CheckResult{ + ver: &MinDepVersion{ + RefPath: "github.com/cloudwego/kitex", + Version: "v0.9.0", + }, + goModVer: "v0.9.1", + }, + expect: func(t *testing.T, res string, shouldExit bool) { + test.Assert(t, shouldExit == false) + test.Assert(t, res == "") + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + prompt, shouldExit := defaultParseCheckResult(tc.cr) + tc.expect(t, prompt, shouldExit) + }) + } +} + +func Test_parseGoToolVersion(t *testing.T) { + testcases := []struct { + desc string + goListStr string + expect string + }{ + { + desc: "normal case without replacement", + goListStr: "github.com/cloudwego/kitex v0.9.0", + expect: "v0.9.0", + }, + { + desc: "normal case with replacement", + goListStr: "github.com/cloudwego/kitex v0.9.0 => github.com/cloudwego/kitex v0.9.1", + expect: "v0.9.1", + }, + { + desc: "replace with local directory", + goListStr: "github.com/cloudwego/kitex v0.9.0 => ./kitex", + expect: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + res := parseGoListVersion(tc.goListStr) + test.Assertf(t, res == tc.expect, "want %s, but got %s", tc.expect, res) + }) + } +} + +func Test_runCommand(t *testing.T) { + t.Run("cmd not found", func(t *testing.T) { + res, err := runCommand("cmd_does_not_exist") + test.Assert(t, errors.Is(err, exec.ErrNotFound), err) + test.Assert(t, res == "") + }) +} + +func Test_runGoListCmd(t *testing.T) { + t.Run("dependency found", func(t *testing.T) { + res, err := runGoListCmd("github.com/cloudwego/thriftgo") + test.Assert(t, err == nil) + test.Assert(t, res != "") + }) + t.Run("dependency not found", func(t *testing.T) { + res, err := runGoListCmd("the/path/that/does/not/exist") + test.Assert(t, errors.Is(err, ErrDependencyNotFound)) + test.Assert(t, res == "") + }) +} + +func Test_generateCheckResult(t *testing.T) { + testcases := []struct { + desc string + depVer func() *MinDepVersion + err error + expect func(t *testing.T, cr *CheckResult) + }{ + { + desc: "err is not nil", + depVer: func() *MinDepVersion { + depVer := &MinDepVersion{ + RefPath: "github.com/cloudwego/kitex", + Version: "v0.9.0", + } + _ = depVer.init() + return depVer + }, + err: ErrDependencyReplacedWithLocalRepo, + expect: func(t *testing.T, cr *CheckResult) { + test.Assert(t, cr != nil) + test.Assert(t, cr.GoModVersion() == "") + test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/kitex") + test.Assert(t, cr.MinDepVersion().Version == "v0.9.0") + test.Assert(t, errors.Is(cr.Err(), ErrDependencyReplacedWithLocalRepo)) + }, + }, + { + desc: "compatible", + depVer: func() *MinDepVersion { + depVer := &MinDepVersion{ + RefPath: "github.com/cloudwego/kitex", + Version: "v0.9.0", + } + _ = depVer.init() + goModVer, _ := newVersion("v0.9.1") + depVer.goModVer = goModVer + return depVer + }, + expect: func(t *testing.T, cr *CheckResult) { + test.Assert(t, cr != nil) + test.Assert(t, cr.GoModVersion() == "v0.9.1") + test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/kitex") + test.Assert(t, cr.MinDepVersion().Version == "v0.9.0") + test.Assert(t, cr.Err() == nil) + }, + }, + { + desc: "incompatible", + depVer: func() *MinDepVersion { + depVer := &MinDepVersion{ + RefPath: "github.com/cloudwego/kitex", + Version: "v0.9.0", + } + _ = depVer.init() + goModVer, _ := newVersion("v0.8.0") + depVer.goModVer = goModVer + return depVer + }, + expect: func(t *testing.T, cr *CheckResult) { + test.Assert(t, cr != nil) + test.Assert(t, cr.GoModVersion() == "v0.8.0") + test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/kitex") + test.Assert(t, cr.MinDepVersion().Version == "v0.9.0") + test.Assert(t, errors.Is(cr.Err(), ErrDependencyVersionNotCompatible)) + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + cr := generateCheckResult(tc.depVer(), tc.err) + tc.expect(t, cr) + }) + } +} diff --git a/tool/cmd/kitex/versions/version.go b/tool/cmd/kitex/versions/version.go new file mode 100644 index 0000000000..d8d2cbfc61 --- /dev/null +++ b/tool/cmd/kitex/versions/version.go @@ -0,0 +1,106 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package versions + +import ( + "fmt" + "regexp" + "strconv" +) + +const ( + partsNum = 3 +) + +var gitSemanticVersionRegexp = regexp.MustCompile(`^v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) + +// version represents a semantic version +// for now, it only parses MAJOR.MINOR.PATCH parts +type version struct { + // MAJOR.MINOR.PATCH + // use slice here for future extensibility + parts []int + // version represented in string + original string + // pre-release part in semantic version + // e.g. v1.0.0-rc1.0.12345678901234-abcd12345678+build.1.0.0 + // pre-release: rc1.0.12345678901234-abcd12345678 + preRelease string + // build-metadata part in semantic version + // e.g. v1.0.0-rc1.0.12345678901234-abcd12345678+build.1.0.0 + // build-metadata: build.1.0.0 + buildMetadata string +} + +func newVersion(verStr string) (*version, error) { + // e.g. v1.0.0-rc1.0.12345678901234-abcd12345678+build.1.0.0 + // subs[0]: "v1.0.0" + // subs[1]: "1" + // subs[2]: "0" + // subs[3]: "0" + // subs[4]: "rc1.0.12345678901234-abcd12345678" + // subs[5]: "build.1.0.0" + subs := gitSemanticVersionRegexp.FindStringSubmatch(verStr) + if subs == nil { + return nil, fmt.Errorf("invalid semantic version: %s", verStr) + } + parts := make([]int, partsNum) + for i := 1; i <= partsNum; i++ { + num, err := strconv.Atoi(subs[i]) + if err != nil { + return nil, err + } + parts[i-1] = num + } + preRelease := subs[4] + buildMetadata := subs[5] + return &version{ + parts: parts, + original: verStr, + preRelease: preRelease, + buildMetadata: buildMetadata, + }, nil +} + +// lessThan verifies whether ver is less than other. +// e.g. v1.0.0 < v1.0.1 +// +// v1.0.0-rc1.0.12345678901234-abcd12345678 < v1.0.0 +func (ver *version) lessThan(other *version) bool { + for i := 0; i < partsNum; i++ { + if ver.parts[i] < other.parts[i] { + return true + } + if ver.parts[i] > other.parts[i] { + return false + } + } + // e.g. v1.0.0-rc1.0.12345678901234-abcd12345678 < v1.0.0 + if (ver.preRelease != "" || ver.buildMetadata != "") && (other.preRelease == "" && other.buildMetadata == "") { + return true + } + return false +} + +func (ver *version) greatOrEqual(other *version) bool { + return !ver.lessThan(other) +} + +func (ver *version) String() string { + if ver == nil { + return "" + } + return ver.original +} diff --git a/tool/cmd/kitex/versions/version_test.go b/tool/cmd/kitex/versions/version_test.go new file mode 100644 index 0000000000..73aa3eee20 --- /dev/null +++ b/tool/cmd/kitex/versions/version_test.go @@ -0,0 +1,118 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package versions + +import ( + "reflect" + "testing" + + "github.com/cloudwego/kitex/internal/test" +) + +func Test_newVersion(t *testing.T) { + testcases := []struct { + verStr string + parts []int + preRelease string + buildMetadata string + expectErr bool + }{ + { + verStr: "v1.0.0", + parts: []int{1, 0, 0}, + }, + { + verStr: "v12.34.56", + parts: []int{12, 34, 56}, + }, + { + verStr: "v1.0.0-rc1.0.12345678901234-abcd12345678+build.1.0.0", + parts: []int{1, 0, 0}, + preRelease: "rc1.0.12345678901234-abcd12345678", + buildMetadata: "build.1.0.0", + }, + { + verStr: "v1.0.0-12345678901234-abcd12345678", + parts: []int{1, 0, 0}, + preRelease: "12345678901234-abcd12345678", + }, + { + verStr: "1.0.0", + expectErr: true, + }, + } + for _, tc := range testcases { + ver, err := newVersion(tc.verStr) + if !tc.expectErr { + test.Assert(t, err == nil, err) + test.Assertf(t, reflect.DeepEqual(tc.parts, ver.parts), "want %v, got %v", tc.parts, ver.parts) + } else { + test.Assert(t, err != nil) + } + + } +} + +func TestVersion_lessThan(t *testing.T) { + testcases := []struct { + base string + other string + expected bool + }{ + { + base: "v0.0.0", + other: "v0.0.1", + expected: true, + }, + { + base: "v0.0.0", + other: "v0.1.0", + expected: true, + }, + { + base: "v0.0.0", + other: "v1.0.0", + expected: true, + }, + { + base: "v1.0.0", + other: "v0.0.0", + expected: false, + }, + { + base: "v1.0.0-rc1.0.12345678901233-abcd12345678", + other: "v1.0.0", + expected: true, + }, + { + base: "v1.0.0", + other: "v1.0.0", + expected: false, + }, + { + base: "v0.2.1", + other: "v1.0.0", + expected: true, + }, + } + + for _, tc := range testcases { + base, err := newVersion(tc.base) + test.Assert(t, err == nil, err) + other, err := newVersion(tc.other) + test.Assert(t, err == nil, err) + test.Assert(t, base.lessThan(other) == tc.expected) + } +} diff --git a/tool/internal_pkg/generator/generator.go b/tool/internal_pkg/generator/generator.go index 5adbfe6cf9..5f792beee1 100644 --- a/tool/internal_pkg/generator/generator.go +++ b/tool/internal_pkg/generator/generator.go @@ -138,6 +138,8 @@ type Config struct { DeepCopyAPI bool Protocol string HandlerReturnKeepResp bool + + NoDependencyCheck bool } // Pack packs the Config into a slice of "key=val" strings. diff --git a/tool/internal_pkg/generator/generator_test.go b/tool/internal_pkg/generator/generator_test.go index f71772a4a6..cccee89e11 100644 --- a/tool/internal_pkg/generator/generator_test.go +++ b/tool/internal_pkg/generator/generator_test.go @@ -69,7 +69,7 @@ func TestConfig_Pack(t *testing.T) { { name: "some", fields: fields{Features: []feature{feature(999)}, ThriftPluginTimeLimit: 30 * time.Second}, - wantRes: []string{"Verbose=false", "GenerateMain=false", "GenerateInvoker=false", "Version=", "NoFastAPI=false", "ModuleName=", "ServiceName=", "Use=", "IDLType=", "Includes=", "ThriftOptions=", "ProtobufOptions=", "Hessian2Options=", "IDL=", "OutputPath=", "PackagePrefix=", "CombineService=false", "CopyIDL=false", "ProtobufPlugins=", "Features=999", "FrugalPretouch=false", "ThriftPluginTimeLimit=30s", "CompilerPath=", "ExtensionFile=", "Record=false", "RecordCmd=", "TemplateDir=", "GenPath=", "DeepCopyAPI=false", "Protocol=", "HandlerReturnKeepResp=false"}, + wantRes: []string{"Verbose=false", "GenerateMain=false", "GenerateInvoker=false", "Version=", "NoFastAPI=false", "ModuleName=", "ServiceName=", "Use=", "IDLType=", "Includes=", "ThriftOptions=", "ProtobufOptions=", "Hessian2Options=", "IDL=", "OutputPath=", "PackagePrefix=", "CombineService=false", "CopyIDL=false", "ProtobufPlugins=", "Features=999", "FrugalPretouch=false", "ThriftPluginTimeLimit=30s", "CompilerPath=", "ExtensionFile=", "Record=false", "RecordCmd=", "TemplateDir=", "GenPath=", "DeepCopyAPI=false", "Protocol=", "HandlerReturnKeepResp=false", "NoDependencyCheck=false"}, }, } for _, tt := range tests { From 6f50af75a12f80f83698bcec12cb0aa048d48fe1 Mon Sep 17 00:00:00 2001 From: QihengZhou Date: Fri, 17 May 2024 14:19:02 +0800 Subject: [PATCH 30/49] feat: add option to specify ip version for default HTTPResolver (#1352) --- pkg/http/resolver.go | 40 +++++++++++++++++++++++++--- pkg/http/resolver_test.go | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/pkg/http/resolver.go b/pkg/http/resolver.go index ebd60be752..be04fff6c6 100644 --- a/pkg/http/resolver.go +++ b/pkg/http/resolver.go @@ -23,16 +23,48 @@ import ( "strconv" ) +const ( + tcp = "tcp" + tcp4 = "tcp4" + tcp6 = "tcp6" +) + // Resolver resolves url to address. type Resolver interface { Resolve(string) (string, error) } -type defaultResolver struct{} +type ResolverOption func(cfg *resolverConfig) + +// WithIPv4 configures the resolver to resolve ipv4 address only. +func WithIPv4() ResolverOption { + return func(cfg *resolverConfig) { + cfg.network = tcp4 + } +} + +// WithIPv6 configures the resolver to resolve ipv6 address only. +func WithIPv6() ResolverOption { + return func(cfg *resolverConfig) { + cfg.network = tcp6 + } +} + +type resolverConfig struct { + network string +} + +type defaultResolver struct { + *resolverConfig +} // NewDefaultResolver creates a default resolver. -func NewDefaultResolver() Resolver { - return &defaultResolver{} +func NewDefaultResolver(options ...ResolverOption) Resolver { + cfg := &resolverConfig{tcp} + for _, option := range options { + option(cfg) + } + return &defaultResolver{cfg} } // Resolve implements the Resolver interface. @@ -49,7 +81,7 @@ func (p *defaultResolver) Resolve(URL string) (string, error) { port = "80" } } - addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port)) + addr, err := net.ResolveTCPAddr(p.network, net.JoinHostPort(host, port)) if err != nil { return "", err } diff --git a/pkg/http/resolver_test.go b/pkg/http/resolver_test.go index 896dbfe4a8..5ba50e7b20 100644 --- a/pkg/http/resolver_test.go +++ b/pkg/http/resolver_test.go @@ -17,6 +17,7 @@ package http import ( + "net" "testing" "github.com/cloudwego/kitex/internal/test" @@ -34,3 +35,57 @@ func TestResolve(t *testing.T) { _, err = r.Resolve(irresolvableURL) test.Assert(t, err != nil) } + +func TestResolverWithIPPolicy(t *testing.T) { + // resolve ipv6 only + v6Resolver := NewDefaultResolver(WithIPv6()) + test.Assert(t, v6Resolver.(*defaultResolver).network == tcp6) + resolvableURL := "http://www.google.com" + ipPort, err := v6Resolver.Resolve(resolvableURL) + test.Assert(t, err == nil) + ip, err := parseIPPort(ipPort) + test.Assert(t, err == nil) + test.Assert(t, isV6(ip)) + + invalidURL := "http://www.)(*&^%$#@!adomainshouldnotexists.com" + _, err = v6Resolver.Resolve(invalidURL) + test.Assert(t, err != nil) + irresolvableURL := "http://www.adomainshouldnotexists.com" + _, err = v6Resolver.Resolve(irresolvableURL) + test.Assert(t, err != nil) + + // resolve ipv4 only + v4Resolver := NewDefaultResolver(WithIPv4()) + test.Assert(t, v4Resolver.(*defaultResolver).network == tcp4) + ipPort, err = v4Resolver.Resolve(resolvableURL) + test.Assert(t, err == nil) + ip, err = parseIPPort(ipPort) + test.Assert(t, err == nil) + test.Assert(t, isV4(ip)) + + // dual + dualResolver := NewDefaultResolver() + test.Assert(t, dualResolver.(*defaultResolver).network == tcp) + ipPort, err = dualResolver.Resolve(resolvableURL) + test.Assert(t, err == nil) + ip, err = parseIPPort(ipPort) + test.Assert(t, err == nil) + test.Assert(t, ip != nil) +} + +func parseIPPort(ipPort string) (net.IP, error) { + host, _, err := net.SplitHostPort(ipPort) + if err != nil { + return nil, err + } + + return net.ParseIP(host), nil +} + +func isV6(ip net.IP) bool { + return ip.To4() == nil && ip.To16() != nil +} + +func isV4(ip net.IP) bool { + return ip.To4() != nil +} From 3e9ff58e9e7fd3b90f990d64cfe1846bf19082e3 Mon Sep 17 00:00:00 2001 From: QihengZhou Date: Fri, 17 May 2024 14:31:33 +0800 Subject: [PATCH 31/49] optimize(gRPC): gRPC onError uses CtxErrorf to print log with information in ctx (#1349) --- pkg/remote/trans/nphttp2/server_handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/remote/trans/nphttp2/server_handler.go b/pkg/remote/trans/nphttp2/server_handler.go index bf05377692..dd99c38915 100644 --- a/pkg/remote/trans/nphttp2/server_handler.go +++ b/pkg/remote/trans/nphttp2/server_handler.go @@ -323,9 +323,9 @@ func (t *svrTransHandler) OnInactive(ctx context.Context, conn net.Conn) { func (t *svrTransHandler) OnError(ctx context.Context, err error, conn net.Conn) { var de *kerrors.DetailedError if ok := errors.As(err, &de); ok && de.Stack() != "" { - klog.Errorf("KITEX: processing gRPC request error, remoteAddr=%s, error=%s\nstack=%s", conn.RemoteAddr(), err.Error(), de.Stack()) + klog.CtxErrorf(ctx, "KITEX: processing gRPC request error, remoteAddr=%s, error=%s\nstack=%s", conn.RemoteAddr(), err.Error(), de.Stack()) } else { - klog.Errorf("KITEX: processing gRPC request error, remoteAddr=%s, error=%s", conn.RemoteAddr(), err.Error()) + klog.CtxErrorf(ctx, "KITEX: processing gRPC request error, remoteAddr=%s, error=%s", conn.RemoteAddr(), err.Error()) } } From a11b1c7815be54bd7482d7ddcf18c4aded48ea07 Mon Sep 17 00:00:00 2001 From: QihengZhou Date: Fri, 17 May 2024 14:41:48 +0800 Subject: [PATCH 32/49] chore: correct the comment of FreezeRPCInfo (#1338) --- pkg/rpcinfo/ctx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/rpcinfo/ctx.go b/pkg/rpcinfo/ctx.go index 37f89387cd..04373a254e 100644 --- a/pkg/rpcinfo/ctx.go +++ b/pkg/rpcinfo/ctx.go @@ -63,7 +63,7 @@ func PutRPCInfo(ri RPCInfo) { // ... // ri := rpcinfo.GetRPCInfo(ctx) // not concurrent-safe // ... -// }(ctx2) +// }(ctx) // // ctx2 := rpcinfo.FreezeRPCInfo(ctx) // this creates a read-only copy of `ri` and attaches it to the new context // go func(ctx context.Context) { From 551e2b269b801f8fe32eeb201b844f4b2e310919 Mon Sep 17 00:00:00 2001 From: YangruiEmma Date: Thu, 23 May 2024 11:52:38 +0800 Subject: [PATCH 33/49] feat(retry): add ctx param for customized result retry funcs (#1353) --- client/option.go | 2 +- client/option_test.go | 12 +- pkg/retry/backup.go | 2 +- pkg/retry/backup_retryer.go | 2 +- pkg/retry/failure.go | 5 +- pkg/retry/failure_retryer.go | 34 ++-- pkg/retry/policy.go | 89 +++++++++-- pkg/retry/retryer.go | 4 +- pkg/retry/retryer_test.go | 300 ++++++++++++++++++++++++++++++++++- 9 files changed, 408 insertions(+), 42 deletions(-) diff --git a/client/option.go b/client/option.go index 8c0310c67b..29a0161908 100644 --- a/client/option.go +++ b/client/option.go @@ -382,7 +382,7 @@ func WithRetryMethodPolicies(mp map[string]retry.Policy) Option { // But if your retry policy is enabled by remote config, WithSpecifiedResultRetry is useful. func WithSpecifiedResultRetry(rr *retry.ShouldResultRetry) Option { return Option{F: func(o *client.Options, di *utils.Slice) { - if rr == nil || (rr.RespRetry == nil && rr.ErrorRetry == nil) { + if rr == nil || !rr.IsValid() { panic(fmt.Errorf("WithSpecifiedResultRetry: invalid '%+v'", rr)) } di.Push(fmt.Sprintf("WithSpecifiedResultRetry(%+v)", rr)) diff --git a/client/option_test.go b/client/option_test.go index f64359f67f..883f67481c 100644 --- a/client/option_test.go +++ b/client/option_test.go @@ -57,26 +57,26 @@ func TestRetryOptionDebugInfo(t *testing.T) { fp.WithDDLStop() expectPolicyStr := "WithFailureRetry({StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:false DDLStop:true " + "CBPolicy:{ErrorRate:0.1}} BackOffPolicy:&{BackOffType:none CfgItems:map[]} RetrySameNode:false ShouldResultRetry:{ErrorRetry:false, RespRetry:false}})" - policyStr := fmt.Sprintf("WithFailureRetry(%+v)", *fp) + policyStr := fmt.Sprintf("WithFailureRetry(%+v)", fp) test.Assert(t, policyStr == expectPolicyStr, policyStr) fp.WithFixedBackOff(10) expectPolicyStr = "WithFailureRetry({StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:false DDLStop:true " + "CBPolicy:{ErrorRate:0.1}} BackOffPolicy:&{BackOffType:fixed CfgItems:map[fix_ms:10]} RetrySameNode:false ShouldResultRetry:{ErrorRetry:false, RespRetry:false}})" - policyStr = fmt.Sprintf("WithFailureRetry(%+v)", *fp) + policyStr = fmt.Sprintf("WithFailureRetry(%+v)", fp) test.Assert(t, policyStr == expectPolicyStr, policyStr) fp.WithRandomBackOff(10, 20) fp.DisableChainRetryStop() expectPolicyStr = "WithFailureRetry({StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:true DDLStop:true " + "CBPolicy:{ErrorRate:0.1}} BackOffPolicy:&{BackOffType:random CfgItems:map[max_ms:20 min_ms:10]} RetrySameNode:false ShouldResultRetry:{ErrorRetry:false, RespRetry:false}})" - policyStr = fmt.Sprintf("WithFailureRetry(%+v)", *fp) + policyStr = fmt.Sprintf("WithFailureRetry(%+v)", fp) test.Assert(t, policyStr == expectPolicyStr, policyStr) fp.WithRetrySameNode() expectPolicyStr = "WithFailureRetry({StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:true DDLStop:true " + "CBPolicy:{ErrorRate:0.1}} BackOffPolicy:&{BackOffType:random CfgItems:map[max_ms:20 min_ms:10]} RetrySameNode:true ShouldResultRetry:{ErrorRetry:false, RespRetry:false}})" - policyStr = fmt.Sprintf("WithFailureRetry(%+v)", *fp) + policyStr = fmt.Sprintf("WithFailureRetry(%+v)", fp) test.Assert(t, policyStr == expectPolicyStr, policyStr) fp.WithSpecifiedResultRetry(&retry.ShouldResultRetry{ErrorRetry: func(err error, ri rpcinfo.RPCInfo) bool { @@ -84,13 +84,13 @@ func TestRetryOptionDebugInfo(t *testing.T) { }}) expectPolicyStr = "WithFailureRetry({StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:true DDLStop:true " + "CBPolicy:{ErrorRate:0.1}} BackOffPolicy:&{BackOffType:random CfgItems:map[max_ms:20 min_ms:10]} RetrySameNode:true ShouldResultRetry:{ErrorRetry:true, RespRetry:false}})" - policyStr = fmt.Sprintf("WithFailureRetry(%+v)", *fp) + policyStr = fmt.Sprintf("WithFailureRetry(%+v)", fp) test.Assert(t, policyStr == expectPolicyStr, policyStr) bp := retry.NewBackupPolicy(20) expectPolicyStr = "WithBackupRequest({RetryDelayMS:20 StopPolicy:{MaxRetryTimes:1 MaxDurationMS:0 DisableChainStop:false " + "DDLStop:false CBPolicy:{ErrorRate:0.1}} RetrySameNode:false})" - policyStr = fmt.Sprintf("WithBackupRequest(%+v)", *bp) + policyStr = fmt.Sprintf("WithBackupRequest(%+v)", bp) test.Assert(t, policyStr == expectPolicyStr, policyStr) WithBackupRequest(bp) } diff --git a/pkg/retry/backup.go b/pkg/retry/backup.go index 8b4a3f7c11..fa3bda5da1 100644 --- a/pkg/retry/backup.go +++ b/pkg/retry/backup.go @@ -68,6 +68,6 @@ func (p *BackupPolicy) WithRetrySameNode() { } // String is used to print human readable debug info. -func (p BackupPolicy) String() string { +func (p *BackupPolicy) String() string { return fmt.Sprintf("{RetryDelayMS:%+v StopPolicy:%+v RetrySameNode:%+v}", p.RetryDelayMS, p.StopPolicy, p.RetrySameNode) } diff --git a/pkg/retry/backup_retryer.go b/pkg/retry/backup_retryer.go index 0741164772..1ff8ff8e3c 100644 --- a/pkg/retry/backup_retryer.go +++ b/pkg/retry/backup_retryer.go @@ -209,7 +209,7 @@ func (r *backupRetryer) UpdatePolicy(rp Policy) (err error) { } // AppendErrMsgIfNeeded implements the Retryer interface. -func (r *backupRetryer) AppendErrMsgIfNeeded(err error, ri rpcinfo.RPCInfo, msg string) { +func (r *backupRetryer) AppendErrMsgIfNeeded(ctx context.Context, err error, ri rpcinfo.RPCInfo, msg string) { if kerrors.IsTimeoutError(err) { // Add additional reason to the error message when timeout occurs but the backup request is not sent. appendErrMsg(err, msg) diff --git a/pkg/retry/failure.go b/pkg/retry/failure.go index bd7b8e2cba..ffb0fdf2ac 100644 --- a/pkg/retry/failure.go +++ b/pkg/retry/failure.go @@ -17,6 +17,7 @@ package retry import ( + "context" "fmt" "time" @@ -112,14 +113,14 @@ func (p *FailurePolicy) WithSpecifiedResultRetry(rr *ShouldResultRetry) { } // String prints human readable information. -func (p FailurePolicy) String() string { +func (p *FailurePolicy) String() string { return fmt.Sprintf("{StopPolicy:%+v BackOffPolicy:%+v RetrySameNode:%+v "+ "ShouldResultRetry:{ErrorRetry:%t, RespRetry:%t}}", p.StopPolicy, p.BackOffPolicy, p.RetrySameNode, p.IsErrorRetryNonNil(), p.IsRespRetryNonNil()) } // AllErrorRetry is common choice for ShouldResultRetry. func AllErrorRetry() *ShouldResultRetry { - return &ShouldResultRetry{ErrorRetry: func(err error, ri rpcinfo.RPCInfo) bool { + return &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { return err != nil }} } diff --git a/pkg/retry/failure_retryer.go b/pkg/retry/failure_retryer.go index a16815c744..11686eb6c4 100644 --- a/pkg/retry/failure_retryer.go +++ b/pkg/retry/failure_retryer.go @@ -138,7 +138,7 @@ func (r *failureRetryer) Do(ctx context.Context, rpcCall RPCCallFunc, firstRI rp circuitbreak.RecordStat(ctx, req, nil, err, cbKey, r.cbContainer.cbCtl, r.cbContainer.cbPanel) } if err == nil { - if r.policy.IsRespRetryNonNil() && r.policy.ShouldResultRetry.RespRetry(resp, cRI) { + if r.policy.IsRespRetry(ctx, resp, cRI) { // user specified resp to do retry continue } @@ -147,7 +147,7 @@ func (r *failureRetryer) Do(ctx context.Context, rpcCall RPCCallFunc, firstRI rp if i == retryTimes { // stop retry then wrap error err = kerrors.ErrRetry.WithCause(err) - } else if !r.isRetryErr(err, cRI) { + } else if !r.isRetryErr(ctx, err, cRI) { // not timeout or user specified error won't do retry break } @@ -204,8 +204,8 @@ func (r *failureRetryer) UpdatePolicy(rp Policy) (err error) { } // AppendErrMsgIfNeeded implements the Retryer interface. -func (r *failureRetryer) AppendErrMsgIfNeeded(err error, ri rpcinfo.RPCInfo, msg string) { - if r.isRetryErr(err, ri) { +func (r *failureRetryer) AppendErrMsgIfNeeded(ctx context.Context, err error, ri rpcinfo.RPCInfo, msg string) { + if r.isRetryErr(ctx, err, ri) { // Add additional reason when retry is not applied. appendErrMsg(err, msg) } @@ -216,7 +216,7 @@ func (r *failureRetryer) Prepare(ctx context.Context, prevRI, retryRI rpcinfo.RP handleRetryInstance(r.policy.RetrySameNode, prevRI, retryRI) } -func (r *failureRetryer) isRetryErr(err error, ri rpcinfo.RPCInfo) bool { +func (r *failureRetryer) isRetryErr(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { if err == nil { return false } @@ -228,7 +228,7 @@ func (r *failureRetryer) isRetryErr(err error, ri rpcinfo.RPCInfo) bool { if r.policy.IsRetryForTimeout() && kerrors.IsTimeoutError(err) { return true } - if r.policy.IsErrorRetryNonNil() && r.policy.ShouldResultRetry.ErrorRetry(err, ri) { + if r.policy.IsErrorRetry(ctx, err, ri) { return true } return false @@ -283,8 +283,11 @@ func (r *failureRetryer) Dump() map[string]interface{} { dm["failure_retry"] = r.policy if r.policy != nil { dm["specified_result_retry"] = map[string]bool{ - "error_retry": r.policy.IsErrorRetryNonNil(), - "resp_retry": r.policy.IsRespRetryNonNil(), + "error_retry": r.policy.IsErrorRetryWithCtxNonNil(), + "resp_retry": r.policy.IsRespRetryWithCtxNonNil(), + // keep it for some versions to confirm the correctness when troubleshooting + "old_error_retry": r.policy.IsErrorRetryNonNil(), + "old_resp_retry": r.policy.IsRespRetryNonNil(), } } if r.errMsg != "" { @@ -298,9 +301,16 @@ func (r *failureRetryer) setSpecifiedResultRetryIfNeeded(rr *ShouldResultRetry) // save the object specified by client.WithSpecifiedResultRetry(..) r.specifiedResultRetry = rr } - if r.policy != nil && r.specifiedResultRetry != nil { - // The priority of client.WithSpecifiedResultRetry(..) is higher, so always update it - // NOTE: client.WithSpecifiedResultRetry(..) will always reject a nil object - r.policy.ShouldResultRetry = r.specifiedResultRetry + if r.policy != nil { + if r.specifiedResultRetry != nil { + // The priority of client.WithSpecifiedResultRetry(..) is higher, so always update it + // NOTE: client.WithSpecifiedResultRetry(..) will always reject a nil object + r.policy.ShouldResultRetry = r.specifiedResultRetry + } + + // even though rr passed from this func is nil, + // the Policy may also have ShouldResultRetry from client.WithFailureRetry or callopt.WithRetryPolicy. + // convertResultRetry is used to convert 'ErrorRetry and RespRetry' to 'ErrorRetryWithCtx and RespRetryWithCtx' + r.policy.ConvertResultRetry() } } diff --git a/pkg/retry/policy.go b/pkg/retry/policy.go index a808bbb902..c3d2a06e77 100644 --- a/pkg/retry/policy.go +++ b/pkg/retry/policy.go @@ -17,6 +17,7 @@ package retry import ( + "context" "fmt" "github.com/cloudwego/kitex/pkg/rpcinfo" @@ -95,21 +96,6 @@ type FailurePolicy struct { Extra string `json:"extra"` } -// IsRespRetryNonNil is used to check if RespRetry is nil -func (p FailurePolicy) IsRespRetryNonNil() bool { - return p.ShouldResultRetry != nil && p.ShouldResultRetry.RespRetry != nil -} - -// IsErrorRetryNonNil is used to check if ErrorRetry is nil -func (p FailurePolicy) IsErrorRetryNonNil() bool { - return p.ShouldResultRetry != nil && p.ShouldResultRetry.ErrorRetry != nil -} - -// IsRetryForTimeout is used to check if timeout error need to retry -func (p FailurePolicy) IsRetryForTimeout() bool { - return p.ShouldResultRetry == nil || !p.ShouldResultRetry.NotRetryForTimeout -} - // BackupPolicy for backup request // DON'T FORGET to update Equals() and DeepCopy() if you add new fields type BackupPolicy struct { @@ -168,8 +154,16 @@ const ( // ShouldResultRetry is used for specifying which error or resp need to be retried type ShouldResultRetry struct { + // ErrorRetryWithCtx is added in v0.10.0, passing ctx is more convenient for user + ErrorRetryWithCtx func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool + // RespRetryWithCtx is added in v0.10.0, passing ctx is more convenient for user + RespRetryWithCtx func(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool + + // Deprecated: please use ErrorRetryWithCtx instead of ErrorRetry ErrorRetry func(err error, ri rpcinfo.RPCInfo) bool - RespRetry func(resp interface{}, ri rpcinfo.RPCInfo) bool + // Deprecated: please use RespRetryWithCtx instead of RespRetry + RespRetry func(resp interface{}, ri rpcinfo.RPCInfo) bool + // disable the default timeout retry in specific scenarios (e.g. the requests are not non-idempotent) NotRetryForTimeout bool } @@ -229,6 +223,65 @@ func (p *FailurePolicy) DeepCopy() *FailurePolicy { } } +// IsRespRetryWithCtxNonNil is used to check if RespRetryWithCtx is nil. +func (p *FailurePolicy) IsRespRetryWithCtxNonNil() bool { + return p.ShouldResultRetry != nil && p.ShouldResultRetry.RespRetryWithCtx != nil +} + +// IsErrorRetryWithCtxNonNil is used to check if ErrorRetryWithCtx is nil +func (p *FailurePolicy) IsErrorRetryWithCtxNonNil() bool { + return p.ShouldResultRetry != nil && p.ShouldResultRetry.ErrorRetryWithCtx != nil +} + +// IsRespRetryNonNil is used to check if RespRetry is nil. +// Deprecated: please use IsRespRetryWithCtxNonNil instead of IsRespRetryNonNil. +func (p *FailurePolicy) IsRespRetryNonNil() bool { + return p.ShouldResultRetry != nil && p.ShouldResultRetry.RespRetry != nil +} + +// IsErrorRetryNonNil is used to check if ErrorRetry is nil. +// Deprecated: please use IsErrorRetryWithCtxNonNil instead of IsErrorRetryNonNil. +func (p *FailurePolicy) IsErrorRetryNonNil() bool { + return p.ShouldResultRetry != nil && p.ShouldResultRetry.ErrorRetry != nil +} + +// IsRetryForTimeout is used to check if timeout error need to retry +func (p *FailurePolicy) IsRetryForTimeout() bool { + return p.ShouldResultRetry == nil || !p.ShouldResultRetry.NotRetryForTimeout +} + +// IsRespRetry is used to check if the resp need to do retry. +func (p *FailurePolicy) IsRespRetry(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool { + // note: actually, it is better to check IsRespRetry to ignore the bad cases, + // but IsRespRetry is a deprecated definition and here will be executed for every call, depends on ConvertResultRetry to ensure the compatibility + return p.IsRespRetryWithCtxNonNil() && p.ShouldResultRetry.RespRetryWithCtx(ctx, resp, ri) +} + +// IsErrorRetry is used to check if the error need to do retry. +func (p *FailurePolicy) IsErrorRetry(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + // note: actually, it is better to check IsErrorRetry to ignore the bad cases, + // but IsErrorRetry is a deprecated definition and here will be executed for every call, depends on ConvertResultRetry to ensure the compatibility + return p.IsErrorRetryWithCtxNonNil() && p.ShouldResultRetry.ErrorRetryWithCtx(ctx, err, ri) +} + +// ConvertResultRetry is used to convert 'ErrorRetry and RespRetry' to 'ErrorRetryWithCtx and RespRetryWithCtx' +func (p *FailurePolicy) ConvertResultRetry() { + if p == nil || p.ShouldResultRetry == nil { + return + } + rr := p.ShouldResultRetry + if rr.ErrorRetry != nil && rr.ErrorRetryWithCtx == nil { + rr.ErrorRetryWithCtx = func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + return rr.ErrorRetry(err, ri) + } + } + if rr.RespRetry != nil && rr.RespRetryWithCtx == nil { + rr.RespRetryWithCtx = func(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool { + return rr.RespRetry(resp, ri) + } + } +} + // Equals to check if BackupPolicy is equal func (p *BackupPolicy) Equals(np *BackupPolicy) bool { if p == nil { @@ -305,6 +358,10 @@ func (p *BackOffPolicy) copyCfgItems() map[BackOffCfgKey]float64 { return cfgItems } +func (rr *ShouldResultRetry) IsValid() bool { + return rr.ErrorRetryWithCtx != nil || rr.RespRetryWithCtx != nil || rr.RespRetry != nil || rr.ErrorRetry != nil +} + func checkCBErrorRate(p *CBPolicy) error { if p.ErrorRate <= 0 || p.ErrorRate > 0.3 { return fmt.Errorf("invalid retry circuit breaker rate, errRate=%0.2f", p.ErrorRate) diff --git a/pkg/retry/retryer.go b/pkg/retry/retryer.go index 828301adec..54fa4cd0e0 100644 --- a/pkg/retry/retryer.go +++ b/pkg/retry/retryer.go @@ -47,7 +47,7 @@ type Retryer interface { // Retry policy execute func. recycleRI is to decide if the firstRI can be recycled. Do(ctx context.Context, rpcCall RPCCallFunc, firstRI rpcinfo.RPCInfo, request interface{}) (lastRI rpcinfo.RPCInfo, recycleRI bool, err error) - AppendErrMsgIfNeeded(err error, ri rpcinfo.RPCInfo, msg string) + AppendErrMsgIfNeeded(ctx context.Context, err error, ri rpcinfo.RPCInfo, msg string) // Prepare to do something needed before retry call. Prepare(ctx context.Context, prevRI, retryRI rpcinfo.RPCInfo) @@ -339,7 +339,7 @@ func (rc *Container) WithRetryIfNeeded(ctx context.Context, callOptRetry *Policy return ri, true, err } if msg != "" { - retryer.AppendErrMsgIfNeeded(err, ri, msg) + retryer.AppendErrMsgIfNeeded(ctx, err, ri, msg) } return ri, false, err } diff --git a/pkg/retry/retryer_test.go b/pkg/retry/retryer_test.go index 20110f6191..66c7d513f0 100644 --- a/pkg/retry/retryer_test.go +++ b/pkg/retry/retryer_test.go @@ -325,7 +325,7 @@ func TestContainer_Dump(t *testing.T) { test.Assert(t, err == nil, err) test.Assert(t, hasCodeCfg == "true") testStr, err = jsoni.MarshalToString(rcDump["test"]) - msg = `{"enable":true,"failure_retry":{"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":""},"specified_result_retry":{"error_retry":false,"resp_retry":false}}` + msg = `{"enable":true,"failure_retry":{"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":""},"specified_result_retry":{"error_retry":false,"old_error_retry":false,"old_resp_retry":false,"resp_retry":false}}` test.Assert(t, err == nil, err) test.Assert(t, testStr == msg, testStr) } @@ -593,6 +593,276 @@ func TestSpecifiedRespRetry(t *testing.T) { test.Assert(t, !ok) } +// test specified error to retry with ErrorRetryWithCtx +func TestSpecifiedErrorRetryWithCtx(t *testing.T) { + retryWithTransError := func(callTimes int32) RPCCallFunc { + // fails for the first call if callTimes is initialized to 0 + return func(ctx context.Context, r Retryer) (rpcinfo.RPCInfo, interface{}, error) { + newVal := atomic.AddInt32(&callTimes, 1) + if newVal == 1 { + return genRPCInfo(), nil, remote.NewTransErrorWithMsg(1000, "mock") + } else { + return genRPCInfoWithRemoteTag(remoteTags), nil, nil + } + } + } + ri := genRPCInfo() + ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri) + + // case1: specified method retry with error + t.Run("case1", func(t *testing.T) { + rc := NewRetryContainer() + shouldResultRetry := &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + if ri.To().Method() == method { + if te, ok := err.(*remote.TransError); ok && te.TypeID() == 1000 { + return true + } + } + return false + }} + err := rc.Init(map[string]Policy{Wildcard: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithTransError(0), ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, !ok) + v, ok := ri.To().Tag(remoteTagKey) + test.Assert(t, ok) + test.Assert(t, v == remoteTagValue) + }) + + // case2: specified method retry with error, but use backup request config cannot be effective + t.Run("case2", func(t *testing.T) { + shouldResultRetry := &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + if ri.To().Method() == method { + if te, ok := err.(*remote.TransError); ok && te.TypeID() == 1000 { + return true + } + } + return false + }} + rc := NewRetryContainer() + err := rc.Init(map[string]Policy{Wildcard: BuildBackupRequest(NewBackupPolicy(10))}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri = genRPCInfo() + _, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithTransError(0), ri, nil) + test.Assert(t, err != nil, err) + test.Assert(t, !ok) + }) + + // case3: specified method retry with error, but method not match + t.Run("case3", func(t *testing.T) { + shouldResultRetry := &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + return ri.To().Method() != method + }} + rc := NewRetryContainer() + err := rc.Init(map[string]Policy{method: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri = genRPCInfo() + ri, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithTransError(0), ri, nil) + test.Assert(t, err != nil) + test.Assert(t, !ok) + _, ok = ri.To().Tag(remoteTagKey) + test.Assert(t, !ok) + }) + + // case4: all error retry + t.Run("case4", func(t *testing.T) { + rc := NewRetryContainer() + p := BuildFailurePolicy(NewFailurePolicyWithResultRetry(AllErrorRetry())) + ri = genRPCInfo() + ri, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTransError(0), ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, !ok) + v, ok := ri.To().Tag(remoteTagKey) + test.Assert(t, ok) + test.Assert(t, v == remoteTagValue) + }) + + // case5: specified method retry with error, only ctx has some info then retry + ctxKeyVal := "ctxKeyVal" + t.Run("case5", func(t *testing.T) { + shouldResultRetry := &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + if ri.To().Method() == method && ctx.Value(ctxKeyVal) == ctxKeyVal { + if te, ok := err.(*remote.TransError); ok && te.TypeID() == 1000 { + return true + } + } + return false + }} + rc := NewRetryContainer() + err := rc.Init(map[string]Policy{method: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri = genRPCInfo() + ctx = context.WithValue(ctx, ctxKeyVal, ctxKeyVal) + ri, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithTransError(0), ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, !ok) + v, ok := ri.To().Tag(remoteTagKey) + test.Assert(t, ok) + test.Assert(t, v == remoteTagValue) + }) +} + +// test specified error to retry, but has both old and new policy, the new one will be effective +func TestSpecifiedErrorRetryHasOldAndNew(t *testing.T) { + retryWithTransError := func(callTimes int32) RPCCallFunc { + // fails for the first call if callTimes is initialized to 0 + return func(ctx context.Context, r Retryer) (rpcinfo.RPCInfo, interface{}, error) { + newVal := atomic.AddInt32(&callTimes, 1) + if newVal == 1 { + return genRPCInfo(), nil, remote.NewTransErrorWithMsg(1000, "mock") + } else { + return genRPCInfoWithRemoteTag(remoteTags), nil, nil + } + } + } + ri := genRPCInfo() + ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri) + + // case1: ErrorRetryWithCtx will retry, but ErrorRetry not retry, the expect result is do retry + t.Run("case1", func(t *testing.T) { + rc := NewRetryContainer() + shouldResultRetry := &ShouldResultRetry{ + ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + return true + }, + ErrorRetry: func(err error, ri rpcinfo.RPCInfo) bool { + return false + }, + } + err := rc.Init(map[string]Policy{Wildcard: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithTransError(0), ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, !ok) + v, ok := ri.To().Tag(remoteTagKey) + test.Assert(t, ok) + test.Assert(t, v == remoteTagValue) + }) + + // case2: ErrorRetryWithCtx not retry, but ErrorRetry retry, the expect result is that not do retry + t.Run("case2", func(t *testing.T) { + shouldResultRetry := &ShouldResultRetry{ + ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + return false + }, + ErrorRetry: func(err error, ri rpcinfo.RPCInfo) bool { + return true + }, + } + rc := NewRetryContainer() + err := rc.Init(map[string]Policy{Wildcard: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithTransError(0), ri, nil) + test.Assert(t, err != nil) + test.Assert(t, !ok) + _, ok = ri.To().Tag(remoteTagKey) + test.Assert(t, !ok) + }) +} + +// test specified resp to retry with ErrorRetryWithCtx +func TestSpecifiedRespRetryWithCtx(t *testing.T) { + retryResult := &mockResult{} + retryResp := mockResp{ + code: 500, + msg: "retry", + } + noRetryResp := mockResp{ + code: 0, + msg: "noretry", + } + var callTimes int32 + retryWithResp := func(ctx context.Context, r Retryer) (rpcinfo.RPCInfo, interface{}, error) { + newVal := atomic.AddInt32(&callTimes, 1) + if newVal == 1 { + retryResult.SetResult(retryResp) + return genRPCInfo(), retryResult, nil + } else { + retryResult.SetResult(noRetryResp) + return genRPCInfoWithRemoteTag(remoteTags), retryResult, nil + } + } + ctx := context.Background() + ri := genRPCInfo() + ctx = rpcinfo.NewCtxWithRPCInfo(ctx, ri) + rc := NewRetryContainer() + // case1: specified method retry with resp + shouldResultRetry := &ShouldResultRetry{RespRetryWithCtx: func(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool { + if ri.To().Method() == method { + if r, ok := resp.(*mockResult); ok && r.GetResult() == retryResp { + return true + } + } + return false + }} + err := rc.Init(map[string]Policy{Wildcard: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithResp, ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, retryResult.GetResult() == noRetryResp, retryResult) + test.Assert(t, !ok) + v, ok := ri.To().Tag(remoteTagKey) + test.Assert(t, ok) + test.Assert(t, v == remoteTagValue) + + // case2 specified method retry with resp, but use backup request config cannot be effective + atomic.StoreInt32(&callTimes, 0) + rc = NewRetryContainer() + err = rc.Init(map[string]Policy{Wildcard: BuildBackupRequest(NewBackupPolicy(100))}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri = genRPCInfo() + ctx = rpcinfo.NewCtxWithRPCInfo(context.Background(), ri) + _, ok, err = rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithResp, ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, retryResult.GetResult() == retryResp, retryResp) + test.Assert(t, !ok) + + // case3: specified method retry with resp, but method not match + atomic.StoreInt32(&callTimes, 0) + shouldResultRetry = &ShouldResultRetry{RespRetryWithCtx: func(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool { + if ri.To().Method() != method { + if r, ok := resp.(*mockResult); ok && r.GetResult() == retryResp { + return true + } + } + return false + }} + rc = NewRetryContainer() + err = rc.Init(map[string]Policy{method: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry) + test.Assert(t, err == nil, err) + ri = genRPCInfo() + ctx = rpcinfo.NewCtxWithRPCInfo(context.Background(), ri) + ri, ok, err = rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithResp, ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, retryResult.GetResult() == retryResp, retryResult) + test.Assert(t, ok) + _, ok = ri.To().Tag(remoteTagKey) + test.Assert(t, !ok) + + // case4: specified method retry with resp, only ctx has some info then retry + ctxKeyVal := "ctxKeyVal" + atomic.StoreInt32(&callTimes, 0) + shouldResultRetry2 := &ShouldResultRetry{RespRetryWithCtx: func(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool { + if ri.To().Method() == method && ctx.Value(ctxKeyVal) == ctxKeyVal { + if r, ok := resp.(*mockResult); ok && r.GetResult() == retryResp { + return true + } + } + return false + }} + err = rc.Init(map[string]Policy{Wildcard: BuildFailurePolicy(NewFailurePolicy())}, shouldResultRetry2) + test.Assert(t, err == nil, err) + ctx = context.WithValue(ctx, ctxKeyVal, ctxKeyVal) + ri, ok, err = rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithResp, ri, nil) + test.Assert(t, err == nil, err) + test.Assert(t, retryResult.GetResult() == noRetryResp, retryResult) + test.Assert(t, !ok) + v, ok = ri.To().Tag(remoteTagKey) + test.Assert(t, ok) + test.Assert(t, v == remoteTagValue) +} + // test different method use different retry policy func TestDifferentMethodConfig(t *testing.T) { var callTimes int32 @@ -689,6 +959,34 @@ func TestResultRetryWithPolicyChange(t *testing.T) { test.Assert(t, fr.policy.ShouldResultRetry == shouldResultRetry) } +func TestResultRetryWithCtxWhenPolicyChange(t *testing.T) { + rc := NewRetryContainer() + shouldResultRetry := &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool { + if ri.To().Method() == method { + if te, ok := err.(*remote.TransError); ok && te.TypeID() == 1000 { + return true + } + } + return false + }} + err := rc.Init(nil, shouldResultRetry) + test.Assert(t, err == nil, err) + + // case 1: first time trigger NotifyPolicyChange, the `initRetryer` will be executed, check if the ShouldResultRetry is not nil + rc.NotifyPolicyChange(Wildcard, BuildFailurePolicy(NewFailurePolicy())) + r := rc.getRetryer(genRPCInfo()) + fr, ok := r.(*failureRetryer) + test.Assert(t, ok) + test.Assert(t, fr.policy.ShouldResultRetry == shouldResultRetry) + + // case 2: second time trigger NotifyPolicyChange, the `UpdatePolicy` will be executed, check if the ShouldResultRetry is not nil + rc.NotifyPolicyChange(Wildcard, BuildFailurePolicy(NewFailurePolicy())) + r = rc.getRetryer(genRPCInfo()) + fr, ok = r.(*failureRetryer) + test.Assert(t, ok) + test.Assert(t, fr.policy.ShouldResultRetry == shouldResultRetry) +} + // test BackupPolicy call while rpcTime > delayTime func TestBackupPolicyCall(t *testing.T) { ctx := context.Background() From 581cc2e538d4c0330d2e0a058686fd48ddf40e22 Mon Sep 17 00:00:00 2001 From: Jayant Date: Thu, 23 May 2024 14:13:08 +0800 Subject: [PATCH 34/49] perf: use dirtmake to reduce memclr cost (#1314) --- go.mod | 2 +- go.sum | 3 ++- pkg/generic/thrift/http.go | 3 ++- pkg/generic/thrift/json.go | 3 ++- pkg/mem/span.go | 14 ++++++++++---- pkg/protocol/bthrift/binary.go | 9 +++++---- pkg/remote/default_bytebuf.go | 12 +++++++----- pkg/remote/trans/nphttp2/buffer_test.go | 5 +++++ pkg/remote/trans/nphttp2/client_conn.go | 6 ++++-- pkg/remote/trans/nphttp2/grpc/framer.go | 3 ++- pkg/remote/trans/nphttp2/server_conn.go | 6 ++++-- pkg/utils/fastthrift/fast_thrift.go | 8 ++++++-- 12 files changed, 50 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 0ff78b779e..090222c564 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/apache/thrift v0.13.0 - github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b + github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1 github.com/bytedance/mockey v1.2.7 github.com/bytedance/sonic v1.11.6 github.com/cloudwego/configmanager v0.2.2 diff --git a/go.sum b/go.sum index c4a8047d3f..ecb07edf56 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,9 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= -github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b h1:R6PWoQtxEMpWJPHnpci+9LgFxCS7iJCfOGBvCgZeTKI= github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= +github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1 h1:rT7Mm6uUpHeZQzfs2v0Mlj0SL02CzyVi+EB7VYPM/z4= +github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/mockey v1.2.7 h1:8j4yCqS5OmMe2dQCxPit4FVkwTK9nrykIgbOZN3s28o= github.com/bytedance/mockey v1.2.7/go.mod h1:bNrUnI1u7+pAc0TYDgPATM+wF2yzHxmNH+iDXg4AOCU= github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw= diff --git a/pkg/generic/thrift/http.go b/pkg/generic/thrift/http.go index 61e6cc8a05..53b15109d2 100644 --- a/pkg/generic/thrift/http.go +++ b/pkg/generic/thrift/http.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/apache/thrift/lib/go/thrift" + "github.com/bytedance/gopkg/lang/dirtmake" "github.com/cloudwego/dynamicgo/conv" "github.com/cloudwego/dynamicgo/conv/t2j" dthrift "github.com/cloudwego/dynamicgo/thrift" @@ -174,7 +175,7 @@ func (r *ReadHTTPResponse) Read(ctx context.Context, method string, in thrift.TP } tyDsc := fnDsc.Response() // json size is usually 2 times larger than equivalent thrift data - buf := make([]byte, 0, len(transBuf)*2) + buf := dirtmake.Bytes(0, len(transBuf)*2) for _, field := range tyDsc.Struct().Fields() { if fid == field.ID() { diff --git a/pkg/generic/thrift/json.go b/pkg/generic/thrift/json.go index 594ef735e2..e769d3f76a 100644 --- a/pkg/generic/thrift/json.go +++ b/pkg/generic/thrift/json.go @@ -22,6 +22,7 @@ import ( "strconv" "github.com/apache/thrift/lib/go/thrift" + "github.com/bytedance/gopkg/lang/dirtmake" "github.com/cloudwego/dynamicgo/conv" "github.com/cloudwego/dynamicgo/conv/t2j" dthrift "github.com/cloudwego/dynamicgo/thrift" @@ -204,7 +205,7 @@ func (m *ReadJSON) Read(ctx context.Context, method string, in thrift.TProtocol) } // json size is usually 2 times larger than equivalent thrift data - buf := make([]byte, 0, len(transBuff)*2) + buf := dirtmake.Bytes(0, len(transBuff)*2) // thrift []byte to json []byte if err := m.t2jBinaryConv.DoInto(ctx, tyDsc, transBuff, &buf); err != nil { return nil, err diff --git a/pkg/mem/span.go b/pkg/mem/span.go index 687e27a2f0..cdc3727ef4 100644 --- a/pkg/mem/span.go +++ b/pkg/mem/span.go @@ -19,6 +19,8 @@ package mem import ( "math/bits" "sync/atomic" + + "github.com/bytedance/gopkg/lang/dirtmake" ) const ( @@ -32,6 +34,8 @@ type spanCache struct { spans [spanCacheSize]*span } +// NewSpanCache returns *spanCache with the given spanSize, +// each span is used to allocate a binary of a specific size level. func NewSpanCache(spanSize int) *spanCache { c := new(spanCache) for i := 0; i < len(c.spans); i++ { @@ -40,10 +44,12 @@ func NewSpanCache(spanSize int) *spanCache { return c } +// Make allocates a binary but does not clear the memory it references. +// NOTE: MUST set any byte element before it's read. func (c *spanCache) Make(n int) []byte { sclass := spanClass(n) - minSpanClass if sclass < 0 || sclass >= len(c.spans) { - return make([]byte, n) + return dirtmake.Bytes(n, n) } return c.spans[sclass].Make(n) } @@ -57,7 +63,7 @@ func (c *spanCache) Copy(buf []byte) (p []byte) { func NewSpan(size int) *span { sp := new(span) sp.size = uint32(size) - sp.buffer = make([]byte, 0, size) + sp.buffer = dirtmake.Bytes(0, size) return sp } @@ -72,7 +78,7 @@ func (b *span) Make(_n int) []byte { n := uint32(_n) if n >= b.size || !atomic.CompareAndSwapUint32(&b.lock, 0, 1) { // fallback path: make a new byte slice if current goroutine cannot get the lock or n is out of size - return make([]byte, n) + return dirtmake.Bytes(int(n), int(n)) } START: b.read += n @@ -83,7 +89,7 @@ START: return buf } // slow path: create a new buffer - b.buffer = make([]byte, b.size) + b.buffer = dirtmake.Bytes(int(b.size), int(b.size)) b.read = 0 goto START } diff --git a/pkg/protocol/bthrift/binary.go b/pkg/protocol/bthrift/binary.go index b1b4024530..2b5dbf3aa8 100644 --- a/pkg/protocol/bthrift/binary.go +++ b/pkg/protocol/bthrift/binary.go @@ -474,17 +474,18 @@ func (binaryProtocol) ReadString(buf []byte) (value string, length int, err erro } func (binaryProtocol) ReadBinary(buf []byte) (value []byte, length int, err error) { - size, l, e := Binary.ReadI32(buf) + _size, l, e := Binary.ReadI32(buf) length += l if e != nil { err = e return } - if size < 0 || int(size) > len(buf) { + size := int(_size) + if size < 0 || size > len(buf) { return value, length, perrors.NewProtocolErrorWithType(thrift.INVALID_DATA, "[ReadBinary] the binary size greater than buf length") } - value = spanCache.Copy(buf[length : length+int(size)]) - length += int(size) + value = spanCache.Copy(buf[length : length+size]) + length += size return } diff --git a/pkg/remote/default_bytebuf.go b/pkg/remote/default_bytebuf.go index 1a8ff17933..fe7b3f3a94 100644 --- a/pkg/remote/default_bytebuf.go +++ b/pkg/remote/default_bytebuf.go @@ -21,6 +21,8 @@ import ( "fmt" "io" "sync" + + "github.com/bytedance/gopkg/lang/dirtmake" ) // Mask bits. @@ -78,7 +80,7 @@ func newWriterByteBuffer(estimatedLength int) ByteBuffer { estimatedLength = 256 // default buffer size } bytebuf := bytebufPool.Get().(*defaultByteBuffer) - bytebuf.buff = make([]byte, estimatedLength) + bytebuf.buff = dirtmake.Bytes(estimatedLength, estimatedLength) bytebuf.status = BitWritable bytebuf.writeIdx = 0 return bytebuf @@ -89,7 +91,7 @@ func newReaderWriterByteBuffer(estimatedLength int) ByteBuffer { estimatedLength = 256 // default buffer size } bytebuf := bytebufPool.Get().(*defaultByteBuffer) - bytebuf.buff = make([]byte, estimatedLength) + bytebuf.buff = dirtmake.Bytes(estimatedLength, estimatedLength) bytebuf.readIdx = 0 bytebuf.writeIdx = 0 bytebuf.status = BitReadable | BitWritable @@ -188,7 +190,7 @@ func (b *defaultByteBuffer) ReadBinary(n int) (p []byte, err error) { if buf, err = b.Next(n); err != nil { return p, err } - p = make([]byte, n) + p = dirtmake.Bytes(n, n) copy(p, buf) return p, nil } @@ -274,7 +276,7 @@ func (b *defaultByteBuffer) Bytes() (buf []byte, err error) { if b.status&BitWritable == 0 { return nil, errors.New("unwritable buffer, cannot support Bytes") } - buf = make([]byte, b.writeIdx) + buf = dirtmake.Bytes(b.writeIdx, b.writeIdx) copy(buf, b.buff[:b.writeIdx]) return buf, nil } @@ -327,7 +329,7 @@ func (b *defaultByteBuffer) ensureWritable(minWritableBytes int) { newCapacity <<= 1 } - buf := make([]byte, newCapacity) + buf := dirtmake.Bytes(newCapacity, newCapacity) copy(buf, b.buff) b.buff = buf } diff --git a/pkg/remote/trans/nphttp2/buffer_test.go b/pkg/remote/trans/nphttp2/buffer_test.go index dba1a175e4..f68ade46b9 100644 --- a/pkg/remote/trans/nphttp2/buffer_test.go +++ b/pkg/remote/trans/nphttp2/buffer_test.go @@ -25,6 +25,11 @@ import ( func TestBuffer(t *testing.T) { // test NewBuffer() grpcConn := newMockNpConn(mockAddr0) + grpcConn.mockConn.ReadFunc = func(b []byte) (n int, err error) { + s := make([]byte, len(b)) + copy(b, s) + return len(b), nil + } buffer := newBuffer(grpcConn) // mock conn only return bytes with 0,0,0..... diff --git a/pkg/remote/trans/nphttp2/client_conn.go b/pkg/remote/trans/nphttp2/client_conn.go index 34929daa2a..e855d30bfa 100644 --- a/pkg/remote/trans/nphttp2/client_conn.go +++ b/pkg/remote/trans/nphttp2/client_conn.go @@ -25,6 +25,8 @@ import ( "strings" "time" + "github.com/bytedance/gopkg/lang/dirtmake" + "github.com/cloudwego/kitex/pkg/remote" "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/codes" "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc" @@ -51,13 +53,13 @@ type clientConn struct { var _ GRPCConn = (*clientConn)(nil) func (c *clientConn) ReadFrame() (hdr, data []byte, err error) { - hdr = make([]byte, 5) + hdr = dirtmake.Bytes(5, 5) _, err = c.Read(hdr) if err != nil { return nil, nil, err } dLen := int(binary.BigEndian.Uint32(hdr[1:])) - data = make([]byte, dLen) + data = dirtmake.Bytes(dLen, dLen) _, err = c.Read(data) if err != nil { return nil, nil, err diff --git a/pkg/remote/trans/nphttp2/grpc/framer.go b/pkg/remote/trans/nphttp2/grpc/framer.go index aadf661129..c27a74cefb 100644 --- a/pkg/remote/trans/nphttp2/grpc/framer.go +++ b/pkg/remote/trans/nphttp2/grpc/framer.go @@ -20,6 +20,7 @@ import ( "io" "net" + "github.com/bytedance/gopkg/lang/dirtmake" "github.com/cloudwego/netpoll" "golang.org/x/net/http2/hpack" @@ -64,7 +65,7 @@ type bufWriter struct { func newBufWriter(writer io.Writer, batchSize int) *bufWriter { return &bufWriter{ - buf: make([]byte, batchSize*2), + buf: dirtmake.Bytes(batchSize*2, batchSize*2), batchSize: batchSize, writer: writer, } diff --git a/pkg/remote/trans/nphttp2/server_conn.go b/pkg/remote/trans/nphttp2/server_conn.go index 80011692d2..4fac524be5 100644 --- a/pkg/remote/trans/nphttp2/server_conn.go +++ b/pkg/remote/trans/nphttp2/server_conn.go @@ -22,6 +22,8 @@ import ( "net" "time" + "github.com/bytedance/gopkg/lang/dirtmake" + "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/codes" "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc" "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/status" @@ -43,13 +45,13 @@ func newServerConn(tr grpc.ServerTransport, s *grpc.Stream) *serverConn { } func (c *serverConn) ReadFrame() (hdr, data []byte, err error) { - hdr = make([]byte, 5) + hdr = dirtmake.Bytes(5, 5) _, err = c.Read(hdr) if err != nil { return nil, nil, err } dLen := int(binary.BigEndian.Uint32(hdr[1:])) - data = make([]byte, dLen) + data = dirtmake.Bytes(dLen, dLen) _, err = c.Read(data) if err != nil { return nil, nil, err diff --git a/pkg/utils/fastthrift/fast_thrift.go b/pkg/utils/fastthrift/fast_thrift.go index 213622cf60..c15d40f4dc 100644 --- a/pkg/utils/fastthrift/fast_thrift.go +++ b/pkg/utils/fastthrift/fast_thrift.go @@ -16,11 +16,15 @@ package fastthrift -import "github.com/cloudwego/kitex/pkg/remote/codec/thrift" +import ( + "github.com/bytedance/gopkg/lang/dirtmake" + + "github.com/cloudwego/kitex/pkg/remote/codec/thrift" +) // FastMarshal marshals the msg to buf. The msg should be generated by Kitex tool and implement ThriftMsgFastCodec. func FastMarshal(msg thrift.ThriftMsgFastCodec) []byte { - buf := make([]byte, msg.BLength()) + buf := dirtmake.Bytes(msg.BLength(), msg.BLength()) msg.FastWriteNocopy(buf, nil) return buf } From a9249d45e43c19b1f692e99d8b589a19f8d9dca3 Mon Sep 17 00:00:00 2001 From: QihengZhou Date: Thu, 23 May 2024 15:29:18 +0800 Subject: [PATCH 35/49] chore: upgrade netpoll to v0.6.1 pre-release version (#1355) --- go.mod | 2 +- go.sum | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 090222c564..c6a56f5476 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/cloudwego/fastpb v0.0.4 github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 - github.com/cloudwego/netpoll v0.6.0 + github.com/cloudwego/netpoll v0.6.1-0.20240516030022-a9a224c3e494 github.com/cloudwego/runtimex v0.1.0 github.com/cloudwego/thriftgo v0.3.6 github.com/golang/mock v1.6.0 diff --git a/go.sum b/go.sum index ecb07edf56..00ae3d5633 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,7 @@ github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= +github.com/bytedance/gopkg v0.0.0-20240507064146-197ded923ae3/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1 h1:rT7Mm6uUpHeZQzfs2v0Mlj0SL02CzyVi+EB7VYPM/z4= github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/mockey v1.2.7 h1:8j4yCqS5OmMe2dQCxPit4FVkwTK9nrykIgbOZN3s28o= @@ -51,8 +52,9 @@ github.com/cloudwego/kitex v0.9.3-dep3/go.mod h1:87pfgsZMaso0tFISQZzMhwmh6pajfVm github.com/cloudwego/kitex v0.9.3-rc/go.mod h1:QurmwA8Wh/s7qz6C+Da9sc9B4TRW6q4TN6Y56mu90SE= github.com/cloudwego/localsession v0.0.2 h1:N9/IDtCPj1fCL9bCTP+DbXx3f40YjVYWcwkJG0YhQkY= github.com/cloudwego/localsession v0.0.2/go.mod h1:kiJxmvAcy4PLgKtEnPS5AXed3xCiXcs7Z+KBHP72Wv8= -github.com/cloudwego/netpoll v0.6.0 h1:JRMkrA1o8k/4quxzg6Q1XM+zIhwZsyoWlq6ef+ht31U= github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= +github.com/cloudwego/netpoll v0.6.1-0.20240516030022-a9a224c3e494 h1:o43pLtNAhtgucplmqwPNHMNTJEmkJ9bcZKpx9dBCu4Y= +github.com/cloudwego/netpoll v0.6.1-0.20240516030022-a9a224c3e494/go.mod h1:kaqvfZ70qd4T2WtIIpCOi5Cxyob8viEpzLhCrTrz3HM= github.com/cloudwego/runtimex v0.1.0 h1:HG+WxWoj5/CDChDZ7D99ROwvSMkuNXAqt6hnhTTZDiI= github.com/cloudwego/runtimex v0.1.0/go.mod h1:23vL/HGV0W8nSCHbe084AgEBdDV4rvXenEUMnUNvUd8= github.com/cloudwego/thriftgo v0.2.11/go.mod h1:dAyXHEmKXo0LfMCrblVEY3mUZsdeuA5+i0vF5f09j7E= From 964e56e06fc04bf42e21f45ed2404dfaa438900c Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Mon, 27 May 2024 11:19:35 +0800 Subject: [PATCH 36/49] feat:(generic) jsonpb using dynamicgo support parse IDL from memory (#1359) --- pkg/generic/pbidl_provider.go | 35 ++++++++++++++++++++++++++++++ pkg/generic/pbidl_provider_test.go | 25 +++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/pkg/generic/pbidl_provider.go b/pkg/generic/pbidl_provider.go index f7d01f0c3b..d115c51054 100644 --- a/pkg/generic/pbidl_provider.go +++ b/pkg/generic/pbidl_provider.go @@ -123,6 +123,41 @@ func NewPbFileProviderWithDynamicGo(main string, ctx context.Context, options dp return p, nil } +// NewPbContentProviderWithDynamicGo creates PbFileProviderWithDynamicGo from memory. +// NOTICE: mainPath is used to store mainContent in includes, thus it MUST NOT conflict with original includes +func NewPbContentProviderWithDynamicGo(ctx context.Context, options dproto.Options, mainPath, mainContent string, includes map[string]string) (PbDescriptorProviderDynamicGo, error) { + p := &PbFileProviderWithDynamicGo{ + svcs: make(chan *dproto.ServiceDescriptor, 1), + } + + sd, err := options.NewDesccriptorFromContent(ctx, mainPath, mainContent, includes) + if err != nil { + return nil, err + } + p.svcs <- sd + + return p, nil +} + +func (p *PbFileProviderWithDynamicGo) UpdateIDL(ctx context.Context, options dproto.Options, mainPath, mainContent string, includes map[string]string) error { + sd, err := options.NewDesccriptorFromContent(ctx, mainPath, mainContent, includes) + if err != nil { + return err + } + + select { + case <-p.svcs: + default: + } + + select { + case p.svcs <- sd: + default: + } + + return nil +} + func (p *PbFileProviderWithDynamicGo) Provide() <-chan *dproto.ServiceDescriptor { return p.svcs } diff --git a/pkg/generic/pbidl_provider_test.go b/pkg/generic/pbidl_provider_test.go index 0d9019ce97..dbd395c566 100644 --- a/pkg/generic/pbidl_provider_test.go +++ b/pkg/generic/pbidl_provider_test.go @@ -18,6 +18,7 @@ package generic import ( "context" + "os" "testing" dproto "github.com/cloudwego/dynamicgo/proto" @@ -72,3 +73,27 @@ func TestPbFileProviderWithDynamicGo(t *testing.T) { svcDsc := <-p.Provide() test.Assert(t, svcDsc != nil) } + +func TestPbContentProviderWithDynamicGo(t *testing.T) { + mainbs, _ := os.ReadFile("./jsonpb_test/idl/echo_import.proto") + main := string(mainbs) + incbs, _ := os.ReadFile("./jsonpb_test/idl/echo.proto") + includes := map[string]string{ + "echo.proto": string(incbs), + } + opts := dproto.Options{} + p, err := NewPbContentProviderWithDynamicGo(context.Background(), opts, "main.proto", main, includes) + + test.Assert(t, err == nil) + + svcDsc := <-p.Provide() + test.Assert(t, svcDsc != nil) + test.Assert(t, svcDsc.Name() == "EchoService") + test.Assert(t, svcDsc.LookupMethodByName("Echo") != nil) + + p.(*PbFileProviderWithDynamicGo).UpdateIDL(context.Background(), opts, "main.proto", main, includes) + svcDsc1 := <-p.Provide() + test.Assert(t, svcDsc1 != nil) + test.Assert(t, svcDsc1.Name() == "EchoService") + test.Assert(t, svcDsc1.LookupMethodByName("Echo") != nil) +} From d7fc1dfd15376efcc4094a7323b1a3a4bd88d5c4 Mon Sep 17 00:00:00 2001 From: Li2CO3 Date: Fri, 31 May 2024 14:09:56 +0800 Subject: [PATCH 37/49] fix: fix grpc compressor mcache free panic when data is empty (#1364) --- pkg/remote/codec/grpc/grpc_compress.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/remote/codec/grpc/grpc_compress.go b/pkg/remote/codec/grpc/grpc_compress.go index 3d257bb9ef..a8fe32a0a9 100644 --- a/pkg/remote/codec/grpc/grpc_compress.go +++ b/pkg/remote/codec/grpc/grpc_compress.go @@ -23,10 +23,10 @@ import ( "errors" "io" - "github.com/cloudwego/kitex/pkg/rpcinfo" - "github.com/bytedance/gopkg/lang/mcache" + "github.com/cloudwego/kitex/pkg/rpcinfo" + "github.com/cloudwego/kitex/pkg/remote/codec/protobuf/encoding" "github.com/cloudwego/kitex/pkg/remote" @@ -63,7 +63,9 @@ func decodeGRPCFrame(ctx context.Context, in remote.ByteBuffer) ([]byte, error) } func compress(compressor encoding.Compressor, data []byte) ([]byte, error) { - defer mcache.Free(data) + if len(data) != 0 { + defer mcache.Free(data) + } cbuf := &bytes.Buffer{} z, err := compressor.Compress(cbuf) if err != nil { From f348aa4647452ca58417e45fa5578a924986acca Mon Sep 17 00:00:00 2001 From: YangruiEmma Date: Sun, 2 Jun 2024 19:05:09 +0800 Subject: [PATCH 38/49] fix(payloadCodec): replace the registered PayloadCodec if the type is same when using WithPayloadCodec for server-side (#1362) --- pkg/remote/codec/protobuf/protobuf.go | 9 +- pkg/remote/codec/thrift/thrift.go | 3 +- server/option.go | 19 +++- server/option_test.go | 133 ++++++++++++++++++++++---- 4 files changed, 140 insertions(+), 24 deletions(-) diff --git a/pkg/remote/codec/protobuf/protobuf.go b/pkg/remote/codec/protobuf/protobuf.go index 3745184d02..a5a11e0aff 100644 --- a/pkg/remote/codec/protobuf/protobuf.go +++ b/pkg/remote/codec/protobuf/protobuf.go @@ -26,6 +26,7 @@ import ( "github.com/cloudwego/kitex/pkg/remote" "github.com/cloudwego/kitex/pkg/remote/codec" "github.com/cloudwego/kitex/pkg/remote/codec/perrors" + "github.com/cloudwego/kitex/pkg/serviceinfo" ) /** @@ -50,6 +51,12 @@ func NewProtobufCodec() remote.PayloadCodec { return &protobufCodec{} } +// IsProtobufCodec checks if the codec is protobufCodec +func IsProtobufCodec(c remote.PayloadCodec) bool { + _, ok := c.(*protobufCodec) + return ok +} + // protobufCodec implements PayloadMarshaler type protobufCodec struct{} @@ -210,7 +217,7 @@ func (c protobufCodec) Unmarshal(ctx context.Context, message remote.Message, in } func (c protobufCodec) Name() string { - return "protobuf" + return serviceinfo.Protobuf.String() } // MessageWriterWithContext writes to output bytebuffer diff --git a/pkg/remote/codec/thrift/thrift.go b/pkg/remote/codec/thrift/thrift.go index 3ec0ed0301..eb9771e965 100644 --- a/pkg/remote/codec/thrift/thrift.go +++ b/pkg/remote/codec/thrift/thrift.go @@ -28,6 +28,7 @@ import ( "github.com/cloudwego/kitex/pkg/remote/codec" "github.com/cloudwego/kitex/pkg/remote/codec/perrors" "github.com/cloudwego/kitex/pkg/rpcinfo" + "github.com/cloudwego/kitex/pkg/serviceinfo" "github.com/cloudwego/kitex/pkg/stats" ) @@ -231,7 +232,7 @@ func validateMessageBeforeDecode(message remote.Message, seqID int32, methodName // Name implements the remote.PayloadCodec interface. func (c thriftCodec) Name() string { - return "thrift" + return serviceinfo.Thrift.String() } // MessageWriterWithContext write to thrift.TProtocol diff --git a/server/option.go b/server/option.go index 59e587d7cd..a9c67fc37d 100644 --- a/server/option.go +++ b/server/option.go @@ -31,9 +31,12 @@ import ( "github.com/cloudwego/kitex/pkg/limiter" "github.com/cloudwego/kitex/pkg/registry" "github.com/cloudwego/kitex/pkg/remote" + "github.com/cloudwego/kitex/pkg/remote/codec/protobuf" + "github.com/cloudwego/kitex/pkg/remote/codec/thrift" "github.com/cloudwego/kitex/pkg/remote/trans/netpollmux" "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc" "github.com/cloudwego/kitex/pkg/rpcinfo" + "github.com/cloudwego/kitex/pkg/serviceinfo" "github.com/cloudwego/kitex/pkg/stats" "github.com/cloudwego/kitex/pkg/streaming" "github.com/cloudwego/kitex/pkg/utils" @@ -207,9 +210,19 @@ func WithCodec(c remote.Codec) Option { // WithPayloadCodec to set a payloadCodec that handle other payload which not support by kitex func WithPayloadCodec(c remote.PayloadCodec) Option { return Option{F: func(o *internal_server.Options, di *utils.Slice) { - di.Push(fmt.Sprintf("WithPayloadCodec(%+v)", c)) - - o.RemoteOpt.PayloadCodec = c + if thrift.IsThriftCodec(c) { + // default thriftCodec has been registered, + // if using NewThriftCodecWithConfig to set codec mode, just replace the registered one + di.Push(fmt.Sprintf("ResetThriftPayloadCodec(%+v)", c)) + remote.PutPayloadCode(serviceinfo.Thrift, c) + } else if protobuf.IsProtobufCodec(c) { + di.Push(fmt.Sprintf("ResetProtobufPayloadCodec(%+v)", c)) + remote.PutPayloadCode(serviceinfo.Protobuf, c) + } else { + di.Push(fmt.Sprintf("WithPayloadCodec(%+v)", c)) + // if specify RemoteOpt.PayloadCodec, then the priority is highest, all payload decode will use this one + o.RemoteOpt.PayloadCodec = c + } }} } diff --git a/server/option_test.go b/server/option_test.go index 5ccb28b16c..1a9609b32e 100644 --- a/server/option_test.go +++ b/server/option_test.go @@ -20,6 +20,7 @@ import ( "context" "math/rand" "net" + "reflect" "testing" "time" @@ -27,8 +28,10 @@ import ( "github.com/cloudwego/kitex/internal/test" "github.com/cloudwego/kitex/pkg/diagnosis" "github.com/cloudwego/kitex/pkg/endpoint" + "github.com/cloudwego/kitex/pkg/generic" "github.com/cloudwego/kitex/pkg/remote" "github.com/cloudwego/kitex/pkg/remote/codec/protobuf" + "github.com/cloudwego/kitex/pkg/remote/codec/thrift" "github.com/cloudwego/kitex/pkg/remote/trans/detection" "github.com/cloudwego/kitex/pkg/remote/trans/netpoll" "github.com/cloudwego/kitex/pkg/remote/trans/netpollmux" @@ -38,6 +41,7 @@ import ( "github.com/cloudwego/kitex/pkg/serviceinfo" "github.com/cloudwego/kitex/pkg/stats" "github.com/cloudwego/kitex/pkg/utils" + "github.com/cloudwego/kitex/transport" ) // TestOptionDebugInfo tests the creation of a server with DebugService option @@ -307,30 +311,115 @@ func TestMuxTransportOption(t *testing.T) { // TestPayloadCodecOption tests the creation of a server with RemoteOpt.PayloadCodec option func TestPayloadCodecOption(t *testing.T) { - svr1 := NewServer() - time.AfterFunc(100*time.Millisecond, func() { - err := svr1.Stop() + t.Run("NotSetPayloadCodec", func(t *testing.T) { + svr := NewServer() + time.AfterFunc(100*time.Millisecond, func() { + err := svr.Stop() + test.Assert(t, err == nil, err) + }) + err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) + test.Assert(t, err == nil, err) + err = svr.Run() test.Assert(t, err == nil, err) + iSvr := svr.(*server) + test.Assert(t, iSvr.opt.RemoteOpt.PayloadCodec == nil) + + tRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Thrift) + tRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err := remote.GetPayloadCodec(tRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, thrift.IsThriftCodec(pc)) + + pRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Protobuf) + pRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err = remote.GetPayloadCodec(pRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, protobuf.IsProtobufCodec(pc)) + }) + t.Run("SetPreRegisteredProtobufCodec", func(t *testing.T) { + svr := NewServer(WithPayloadCodec(protobuf.NewProtobufCodec())) + time.AfterFunc(100*time.Millisecond, func() { + err := svr.Stop() + test.Assert(t, err == nil, err) + }) + err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) + test.Assert(t, err == nil, err) + err = svr.Run() + test.Assert(t, err == nil, err) + iSvr := svr.(*server) + test.Assert(t, iSvr.opt.RemoteOpt.PayloadCodec == nil) + + tRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Thrift) + tRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err := remote.GetPayloadCodec(tRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, thrift.IsThriftCodec(pc)) + + pRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Protobuf) + pRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err = remote.GetPayloadCodec(pRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, protobuf.IsProtobufCodec(pc)) }) - err := svr1.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) - test.Assert(t, err == nil, err) - err = svr1.Run() - test.Assert(t, err == nil, err) - iSvr1 := svr1.(*server) - test.Assert(t, iSvr1.opt.RemoteOpt.PayloadCodec == nil) - svr2 := NewServer(WithPayloadCodec(protobuf.NewProtobufCodec())) - time.AfterFunc(100*time.Millisecond, func() { - err := svr2.Stop() + t.Run("SetPreRegisteredThriftCodec", func(t *testing.T) { + thriftCodec := thrift.NewThriftCodecDisableFastMode(false, true) + svr := NewServer(WithPayloadCodec(thriftCodec)) + time.AfterFunc(100*time.Millisecond, func() { + err := svr.Stop() + test.Assert(t, err == nil, err) + }) + err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) + test.Assert(t, err == nil, err) + err = svr.Run() test.Assert(t, err == nil, err) + iSvr := svr.(*server) + test.Assert(t, iSvr.opt.RemoteOpt.PayloadCodec == nil) + + tRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Thrift) + tRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err := remote.GetPayloadCodec(tRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, thrift.IsThriftCodec(pc)) + test.Assert(t, reflect.DeepEqual(pc, thriftCodec)) + + pRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Protobuf) + pRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err = remote.GetPayloadCodec(pRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, protobuf.IsProtobufCodec(pc)) + }) + + t.Run("SetNonPreRegisteredCodec", func(t *testing.T) { + // generic.BinaryThriftGeneric().PayloadCodec() is not the pre registered codec, RemoteOpt.PayloadCodec won't be nil + binaryThriftCodec := generic.BinaryThriftGeneric().PayloadCodec() + svr := NewServer(WithPayloadCodec(binaryThriftCodec)) + time.AfterFunc(100*time.Millisecond, func() { + err := svr.Stop() + test.Assert(t, err == nil, err) + }) + err := svr.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) + test.Assert(t, err == nil, err) + err = svr.Run() + test.Assert(t, err == nil, err) + iSvr := svr.(*server) + test.Assert(t, iSvr.opt.RemoteOpt.PayloadCodec != nil) + test.DeepEqual(t, iSvr.opt.RemoteOpt.PayloadCodec, binaryThriftCodec) + + tRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Thrift) + tRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err := remote.GetPayloadCodec(tRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, !thrift.IsThriftCodec(pc)) + test.Assert(t, reflect.DeepEqual(pc, binaryThriftCodec)) + + pRecvMsg := NewRemoteMsgWithPayloadType(serviceinfo.Protobuf) + pRecvMsg.SetPayloadCodec(iSvr.opt.RemoteOpt.PayloadCodec) + pc, err = remote.GetPayloadCodec(pRecvMsg) + test.Assert(t, err == nil) + test.Assert(t, !protobuf.IsProtobufCodec(pc)) + test.Assert(t, reflect.DeepEqual(pc, binaryThriftCodec)) }) - err = svr2.RegisterService(mocks.ServiceInfo(), mocks.MyServiceHandler()) - test.Assert(t, err == nil, err) - err = svr2.Run() - test.Assert(t, err == nil, err) - iSvr2 := svr2.(*server) - test.Assert(t, iSvr2.opt.RemoteOpt.PayloadCodec != nil) - test.DeepEqual(t, iSvr2.opt.RemoteOpt.PayloadCodec, protobuf.NewProtobufCodec()) } // TestRemoteOptGRPCCfgUintValueOption tests the creation of a server with RemoteOpt.GRPCCfg option @@ -474,3 +563,9 @@ func TestRefuseTrafficWithoutServiceNamOption(t *testing.T) { iSvr := svr.(*server) test.Assert(t, iSvr.opt.RefuseTrafficWithoutServiceName) } + +func NewRemoteMsgWithPayloadType(ct serviceinfo.PayloadCodec) remote.Message { + remoteMsg := remote.NewMessage(nil, nil, nil, remote.Call, remote.Server) + remoteMsg.SetProtocolInfo(remote.NewProtocolInfo(transport.TTHeader, ct)) + return remoteMsg +} From 7c432e624cb19b992dff3f2a7a828579b692495d Mon Sep 17 00:00:00 2001 From: Kyle Xiao Date: Mon, 3 Jun 2024 16:36:01 +0800 Subject: [PATCH 39/49] perf(thrift): optimized skip decoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit goos: darwin goarch: arm64 pkg: github.com/cloudwego/kitex/pkg/remote/codec/thrift │ ./old.txt │ ./new.txt │ │ sec/op │ sec/op vs base │ SkipDecoder-12 336.8n ± ∞ ¹ 129.0n ± ∞ ¹ -61.70% (p=0.008 n=5) --- pkg/remote/codec/thrift/skip_decoder.go | 217 ++++++++----------- pkg/remote/codec/thrift/skip_decoder_test.go | 52 ++--- pkg/remote/codec/thrift/thrift_data.go | 10 +- pkg/remote/codec/thrift/thrift_data_test.go | 2 +- 4 files changed, 122 insertions(+), 159 deletions(-) diff --git a/pkg/remote/codec/thrift/skip_decoder.go b/pkg/remote/codec/thrift/skip_decoder.go index 3da60b042e..1f3ca8d5fc 100644 --- a/pkg/remote/codec/thrift/skip_decoder.go +++ b/pkg/remote/codec/thrift/skip_decoder.go @@ -17,6 +17,7 @@ package thrift import ( + "encoding/binary" "errors" "fmt" @@ -30,172 +31,142 @@ const ( EnableSkipDecoder CodecType = 0b10000 ) -// skipBuffer wraps remote.ByteBuffer and reimplement the Next method. -// Next method would not advance the reader in the underlying buffer until Buffer method is called. -// It is used to fit in BinaryProtocol and reduce memory allocation. -type skipBuffer struct { - remote.ByteBuffer - readNum int -} - -func (b *skipBuffer) Next(n int) ([]byte, error) { - prev := b.readNum - next := prev + n - buf, err := b.ByteBuffer.Peek(next) - if err != nil { - return nil, err - } - b.readNum = next - return buf[prev:next], nil -} - -func (b *skipBuffer) Buffer() ([]byte, error) { - return b.ByteBuffer.Next(b.readNum) -} - -func newSkipBuffer(bb remote.ByteBuffer) *skipBuffer { - return &skipBuffer{ - ByteBuffer: bb, - } -} - // skipDecoder is used to parse the input byte-by-byte and skip the thrift payload // for making use of Frugal and FastCodec in standard Thrift Binary Protocol scenario. type skipDecoder struct { - tprot *BinaryProtocol - sb *skipBuffer -} + remote.ByteBuffer -func newSkipDecoder(trans remote.ByteBuffer) *skipDecoder { - sb := newSkipBuffer(trans) - return &skipDecoder{ - tprot: NewBinaryProtocol(sb), - sb: sb, - } + r int + buf []byte } -func (sd *skipDecoder) SkipStruct() error { - return sd.skip(thrift.STRUCT, thrift.DEFAULT_RECURSION_DEPTH) +func (p *skipDecoder) init() (err error) { + p.r = 0 + p.buf, err = p.Peek(p.ReadableLen()) + return } -func (sd *skipDecoder) skipString() error { - size, err := sd.tprot.ReadI32() - if err != nil { - return err - } - if size < 0 { - return perrors.InvalidDataLength +func (p *skipDecoder) loadmore(n int) error { + // trigger underlying conn to read more + _, err := p.Peek(n) + if err == nil { + // read as much as possible, luckly, we will have a full buffer + // then we no need to call p.Peek many times + p.buf, err = p.Peek(p.ReadableLen()) } - _, err = sd.tprot.next(int(size)) return err } -func (sd *skipDecoder) skipMap(maxDepth int) error { - keyTypeId, valTypeId, size, err := sd.tprot.ReadMapBegin() - if err != nil { - return err - } - for i := 0; i < size; i++ { - if err = sd.skip(keyTypeId, maxDepth); err != nil { - return err - } - if err = sd.skip(valTypeId, maxDepth); err != nil { - return err +func (p *skipDecoder) next(n int) ([]byte, error) { + if len(p.buf)-p.r < n { + if err := p.loadmore(p.r + n); err != nil { + return nil, err } + // after calling p.loadmore, p.buf MUST be at least (p.r + n) len } - return nil + ret := p.buf[p.r : p.r+n] + p.r += n + return ret, nil } -func (sd *skipDecoder) skipList(maxDepth int) error { - elemTypeId, size, err := sd.tprot.ReadListBegin() - if err != nil { - return err +func (p *skipDecoder) NextStruct() (buf []byte, err error) { + // should be ok with init, just one less p.Peek call + if err := p.init(); err != nil { + return nil, err } - for i := 0; i < size; i++ { - if err = sd.skip(elemTypeId, maxDepth); err != nil { - return err - } + if err := p.skip(thrift.STRUCT, thrift.DEFAULT_RECURSION_DEPTH); err != nil { + return nil, err } - return nil -} - -func (sd *skipDecoder) skipSet(maxDepth int) error { - return sd.skipList(maxDepth) + return p.Next(p.r) } -func (sd *skipDecoder) skip(typeId thrift.TType, maxDepth int) (err error) { +// skip skips bytes for a specific type. +// After calling skip, p.r contains the len of the type. +// Since BinaryProtocol calls Next of remote.ByteBuffer many times when reading a struct, +// we don't use it for performance concerns +func (p *skipDecoder) skip(typeID thrift.TType, maxDepth int) error { if maxDepth <= 0 { return thrift.NewTProtocolExceptionWithType(thrift.DEPTH_LIMIT, errors.New("depth limit exceeded")) } - - switch typeId { + switch typeID { case thrift.BOOL, thrift.BYTE: - if _, err = sd.tprot.next(1); err != nil { - return + if _, err := p.next(1); err != nil { + return err } case thrift.I16: - if _, err = sd.tprot.next(2); err != nil { - return + if _, err := p.next(2); err != nil { + return err } case thrift.I32: - if _, err = sd.tprot.next(4); err != nil { - return + if _, err := p.next(4); err != nil { + return err } case thrift.I64, thrift.DOUBLE: - if _, err = sd.tprot.next(8); err != nil { - return + if _, err := p.next(8); err != nil { + return err } case thrift.STRING: - if err = sd.skipString(); err != nil { - return + b, err := p.next(4) + if err != nil { + return err + } + sz := int(binary.BigEndian.Uint32(b)) + if sz < 0 { + return perrors.InvalidDataLength + } + if _, err := p.next(sz); err != nil { + return err } case thrift.STRUCT: - if err = sd.skipStruct(maxDepth - 1); err != nil { - return + for { + b, err := p.next(1) // TType + if err != nil { + return err + } + tp := thrift.TType(b[0]) + if tp == thrift.STOP { + break + } + if _, err := p.next(2); err != nil { // Field ID + return err + } + if err := p.skip(tp, maxDepth-1); err != nil { + return err + } } case thrift.MAP: - if err = sd.skipMap(maxDepth - 1); err != nil { - return + b, err := p.next(6) // 1 byte key TType, 1 byte value TType, 4 bytes Len + if err != nil { + return err } - case thrift.SET: - if err = sd.skipSet(maxDepth - 1); err != nil { - return + kt, vt, sz := thrift.TType(b[0]), thrift.TType(b[1]), int32(binary.BigEndian.Uint32(b[2:])) + if sz < 0 { + return perrors.InvalidDataLength } - case thrift.LIST: - if err = sd.skipList(maxDepth - 1); err != nil { - return + for i := int32(0); i < sz; i++ { + if err := p.skip(kt, maxDepth-1); err != nil { + return err + } + if err := p.skip(vt, maxDepth-1); err != nil { + return err + } } - default: - return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("unknown data type %d", typeId)) - } - return nil -} - -func (sd *skipDecoder) skipStruct(maxDepth int) (err error) { - var fieldTypeId thrift.TType - - for { - _, fieldTypeId, _, err = sd.tprot.ReadFieldBegin() + case thrift.SET, thrift.LIST: + b, err := p.next(5) // 1 byte value type, 4 bytes Len if err != nil { return err } - if fieldTypeId == thrift.STOP { - return err + vt, sz := thrift.TType(b[0]), int32(binary.BigEndian.Uint32(b[1:])) + if sz < 0 { + return perrors.InvalidDataLength } - if err = sd.skip(fieldTypeId, maxDepth); err != nil { - return err + for i := int32(0); i < sz; i++ { + if err := p.skip(vt, maxDepth-1); err != nil { + return err + } } + default: + return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("unknown data type %d", typeID)) } -} - -// Buffer returns the skipped buffer. -// Using this buffer to feed to Frugal or FastCodec -func (sd *skipDecoder) Buffer() ([]byte, error) { - return sd.sb.Buffer() -} - -// Recycle recycles the internal BinaryProtocol and would not affect the buffer -// returned by Buffer() -func (sd *skipDecoder) Recycle() { - sd.tprot.Recycle() + return nil } diff --git a/pkg/remote/codec/thrift/skip_decoder_test.go b/pkg/remote/codec/thrift/skip_decoder_test.go index d80cfc05d5..3da2bf6388 100644 --- a/pkg/remote/codec/thrift/skip_decoder_test.go +++ b/pkg/remote/codec/thrift/skip_decoder_test.go @@ -25,23 +25,7 @@ import ( "github.com/cloudwego/kitex/pkg/remote" ) -func TestSkipBuffer(t *testing.T) { - buf := []byte{1, 2, 3} - rb := remote.NewReaderBuffer(buf) - sb := newSkipBuffer(rb) - peekData, err := sb.Next(3) - test.Assert(t, err == nil) - test.DeepEqual(t, buf, peekData[:len(buf)]) - test.Assert(t, sb.readNum == len(buf)) - test.Assert(t, rb.ReadLen() == 0) - - data, err := sb.Buffer() - test.Assert(t, err == nil) - test.DeepEqual(t, buf, data[:len(buf)]) - test.Assert(t, rb.ReadLen() == len(buf)) -} - -func TestSkipDecoder_SkipStruct(t *testing.T) { +func makeByteBufferForSkipDecoderTest() remote.ByteBuffer { tProt := NewBinaryProtocol(remote.NewReaderWriterBuffer(1024)) defer tProt.Recycle() tProt.WriteStructBegin("testStruct") @@ -95,17 +79,29 @@ func TestSkipDecoder_SkipStruct(t *testing.T) { tProt.WriteFieldStop() tProt.WriteStructEnd() + return tProt.ByteBuffer() +} - length := tProt.ByteBuffer().ReadableLen() - sd := newSkipDecoder(tProt.ByteBuffer()) - defer sd.Recycle() - err := sd.SkipStruct() +func TestSkipDecoder_NextStruct(t *testing.T) { + buf := makeByteBufferForSkipDecoderTest() + defer buf.Release(nil) + length := buf.ReadableLen() + sd := skipDecoder{ByteBuffer: buf} + b, err := sd.NextStruct() test.Assert(t, err == nil) - test.Assert(t, sd.sb.readNum == length) - test.Assert(t, sd.sb.ReadLen() == 0) - test.Assert(t, sd.sb.ReadableLen() == length) - _, err = sd.Buffer() - test.Assert(t, err == nil) - test.Assert(t, sd.sb.ReadLen() == length) - test.Assert(t, sd.sb.ReadableLen() == 0) + test.Assert(t, len(b) == length) +} + +func BenchmarkSkipDecoder(b *testing.B) { + buf := makeByteBufferForSkipDecoderTest() + for i := 0; i < b.N; i++ { + b, _ := buf.Peek(buf.ReadableLen()) + bb := remote.NewReaderBuffer(b) + + sd := skipDecoder{ByteBuffer: bb} + sd.NextStruct() + + bb.Release(nil) + } + buf.Release(nil) } diff --git a/pkg/remote/codec/thrift/thrift_data.go b/pkg/remote/codec/thrift/thrift_data.go index 6ec0d08fce..b350d6191f 100644 --- a/pkg/remote/codec/thrift/thrift_data.go +++ b/pkg/remote/codec/thrift/thrift_data.go @@ -252,14 +252,10 @@ func decodeBasicThriftData(ctx context.Context, tProt thrift.TProtocol, method s } func getSkippedStructBuffer(tProt *BinaryProtocol) ([]byte, error) { - sd := newSkipDecoder(tProt.trans) - defer sd.Recycle() - if err := sd.SkipStruct(); err != nil { - return nil, remote.NewTransError(remote.ProtocolError, err).AppendMessage("caught in SkipDecoder SkipStruct phase") - } - buf, err := sd.Buffer() + sd := skipDecoder{ByteBuffer: tProt.trans} + buf, err := sd.NextStruct() if err != nil { - return nil, remote.NewTransError(remote.ProtocolError, err).AppendMessage("caught in SkipDecoder Buffer phase") + return nil, remote.NewTransError(remote.ProtocolError, err).AppendMessage("caught in SkipDecoder NextStruct phase") } return buf, nil } diff --git a/pkg/remote/codec/thrift/thrift_data_test.go b/pkg/remote/codec/thrift/thrift_data_test.go index d56cf0b55a..8ceebb148a 100644 --- a/pkg/remote/codec/thrift/thrift_data_test.go +++ b/pkg/remote/codec/thrift/thrift_data_test.go @@ -197,5 +197,5 @@ func Test_getSkippedStructBuffer(t *testing.T) { tProt := NewBinaryProtocol(remote.NewReaderBuffer(faultThrift)) _, err := getSkippedStructBuffer(tProt) test.Assert(t, err != nil, err) - test.Assert(t, strings.Contains(err.Error(), "caught in SkipDecoder SkipStruct phase")) + test.Assert(t, strings.Contains(err.Error(), "caught in SkipDecoder NextStruct phase")) } From 1f95c72175ed3b8dea21cd03035444a719664890 Mon Sep 17 00:00:00 2001 From: Joway Date: Tue, 4 Jun 2024 14:59:03 +0800 Subject: [PATCH 40/49] fix: re-cap bytes when Make (#1361) --- pkg/mem/span.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/mem/span.go b/pkg/mem/span.go index cdc3727ef4..3112ffb2e2 100644 --- a/pkg/mem/span.go +++ b/pkg/mem/span.go @@ -23,6 +23,13 @@ import ( "github.com/bytedance/gopkg/lang/dirtmake" ) +/* Span Cache: A thread-safe linear allocator + +Design: +1. [GC Friendly]: Centralize a batch of small bytes slice into a big size bytes slice to avoid malloc too many objects +2. [Thread Safe]: Multi thread may share a same span, but it should fall back to the native allocator if lock conflict +*/ + const ( spanCacheSize = 10 minSpanObject = 128 // 128 B @@ -34,7 +41,7 @@ type spanCache struct { spans [spanCacheSize]*span } -// NewSpanCache returns *spanCache with the given spanSize, +// NewSpanCache returns a spanCache with the given spanSize, // each span is used to allocate a binary of a specific size level. func NewSpanCache(spanSize int) *spanCache { c := new(spanCache) @@ -44,8 +51,8 @@ func NewSpanCache(spanSize int) *spanCache { return c } -// Make allocates a binary but does not clear the memory it references. -// NOTE: MUST set any byte element before it's read. +// Make returns a [:n:n] bytes slice from a cached buffer +// NOTE: Make will not clear the underlay bytes for performance concern. So caller MUST set every byte before read. func (c *spanCache) Make(n int) []byte { sclass := spanClass(n) - minSpanClass if sclass < 0 || sclass >= len(c.spans) { @@ -54,12 +61,14 @@ func (c *spanCache) Make(n int) []byte { return c.spans[sclass].Make(n) } +// Copy is an alias function for make-and-copy pattern func (c *spanCache) Copy(buf []byte) (p []byte) { p = c.Make(len(buf)) copy(p, buf) return p } +// NewSpan returns a span with given size func NewSpan(size int) *span { sp := new(span) sp.size = uint32(size) @@ -74,6 +83,8 @@ type span struct { buffer []byte } +// Make returns a [:n:n] bytes slice from a cached buffer +// NOTE: Make will not clear the underlay bytes for performance concern. So caller MUST set every byte before read. func (b *span) Make(_n int) []byte { n := uint32(_n) if n >= b.size || !atomic.CompareAndSwapUint32(&b.lock, 0, 1) { @@ -84,7 +95,7 @@ START: b.read += n // fast path if b.read <= b.size { - buf := b.buffer[b.read-n : b.read] + buf := b.buffer[b.read-n : b.read : b.read] atomic.StoreUint32(&b.lock, 0) return buf } @@ -94,6 +105,7 @@ START: goto START } +// Copy is an alias function for make-and-copy pattern func (b *span) Copy(buf []byte) (p []byte) { p = b.Make(len(buf)) copy(p, buf) From 3898a01d6198f829168dbfb12a340cbd1dafec27 Mon Sep 17 00:00:00 2001 From: Marina Sakai <118230951+Marina-Sakai@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:12:28 +0800 Subject: [PATCH 41/49] fix(remote): modify the error message thrown when no target service is found (#1368) --- pkg/remote/message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/remote/message.go b/pkg/remote/message.go index 57749e385a..5e825b0696 100644 --- a/pkg/remote/message.go +++ b/pkg/remote/message.go @@ -201,7 +201,7 @@ func (m *message) SpecifyServiceInfo(svcName, methodName string) (*serviceinfo.S } svcInfo := m.svcSearchMap[key] if svcInfo == nil { - return nil, NewTransErrorWithMsg(UnknownMethod, fmt.Sprintf("unknown method %s", methodName)) + return nil, NewTransErrorWithMsg(UnknownService, fmt.Sprintf("unknown service %s, method %s", svcName, methodName)) } m.targetSvcInfo = svcInfo return svcInfo, nil From 188eebafcf31e364b516943e247054767e9aec38 Mon Sep 17 00:00:00 2001 From: Joway Date: Thu, 6 Jun 2024 10:29:09 +0800 Subject: [PATCH 42/49] fix: init default values when using liner allocator (#1374) --- tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go index 4be29292a1..b1f4e375e9 100644 --- a/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go +++ b/tool/internal_pkg/pluginmode/thriftgo/struct_tpl.go @@ -514,6 +514,7 @@ const FieldFastReadMap = ` {{- $ctx := (.ValCtx.WithTarget $val).WithFieldMask $curFieldMask}} {{- if $isStructVal}} {{$val}} := &values[i] + {{$val}}.InitDefault() {{- else}} {{- $ctx = $ctx.WithDecl}} {{- end}} @@ -564,6 +565,7 @@ const FieldFastReadSet = ` {{- $ctx := (.ValCtx.WithTarget $val).WithFieldMask $curFieldMask}} {{- if $isStructVal}} {{$val}} := &values[i] + {{$val}}.InitDefault() {{- else}} {{- $ctx = $ctx.WithDecl}} {{- end}} @@ -614,6 +616,7 @@ const FieldFastReadList = ` {{- $ctx := (.ValCtx.WithTarget $val).WithFieldMask $curFieldMask}} {{- if $isStructVal}} {{$val}} := &values[i] + {{$val}}.InitDefault() {{- else}} {{- $ctx = $ctx.WithDecl}} {{- end}} From b9d6a550a17758312dad8df9bebca4e685c47eae Mon Sep 17 00:00:00 2001 From: YangruiEmma Date: Thu, 6 Jun 2024 15:05:52 +0800 Subject: [PATCH 43/49] chore: integration test use go 1.20 to solve the compatibility issue of offical gRPC in kitex-tests repo (#1376) --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 25eaa7618d..16f4b6c180 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '1.17' + go-version: '1.20' - name: Unit Test run: go test -gcflags=-l -race -covermode=atomic -coverprofile=coverage.txt ./... - name: Scenario Tests @@ -31,7 +31,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '1.17' + go-version: '1.18' - name: Benchmark run: go test -gcflags='all=-N -l' -bench=. -benchmem -run=none ./... @@ -57,7 +57,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '1.17' + go-version: '1.19' - name: Prepare run: | go install github.com/cloudwego/thriftgo@main From bb385e75ffd9f21afacee59f7123c3f51d0942c1 Mon Sep 17 00:00:00 2001 From: Scout Wang Date: Fri, 7 Jun 2024 19:02:15 +0800 Subject: [PATCH 44/49] feat(loadbalance): do not cache all the keys for Consistent Hash (#1370) --- pkg/loadbalance/consist.go | 220 ++++++++++---------------------- pkg/loadbalance/consist_test.go | 164 +++++++++++++++++------- 2 files changed, 187 insertions(+), 197 deletions(-) diff --git a/pkg/loadbalance/consist.go b/pkg/loadbalance/consist.go index a3adf66635..df56102e93 100644 --- a/pkg/loadbalance/consist.go +++ b/pkg/loadbalance/consist.go @@ -19,16 +19,13 @@ package loadbalance import ( "context" "sort" - "strconv" "sync" - "sync/atomic" "time" "github.com/bytedance/gopkg/util/xxhash3" "golang.org/x/sync/singleflight" "github.com/cloudwego/kitex/pkg/discovery" - "github.com/cloudwego/kitex/pkg/gofunc" "github.com/cloudwego/kitex/pkg/utils" ) @@ -76,6 +73,7 @@ type ConsistentHashOption struct { // It is recommended to set it to true, but be careful to reduce the VirtualFactor appropriately Weighted bool + // Deprecated: This implementation will not cache all the keys anymore, ExpireDuration will not take effect // Whether or not to perform expiration processing // The implementation will cache all the keys // If never expired it may cause memory to keep growing and eventually OOM @@ -88,20 +86,14 @@ type ConsistentHashOption struct { // NewConsistentHashOption creates a default ConsistentHashOption. func NewConsistentHashOption(f KeyFunc) ConsistentHashOption { return ConsistentHashOption{ - GetKey: f, - Replica: 0, - VirtualFactor: 100, - Weighted: true, - ExpireDuration: 2 * time.Minute, + GetKey: f, + Replica: 0, + VirtualFactor: 100, + Weighted: true, } } -var ( - consistPickerPool sync.Pool - consistBalancers []*consistBalancer - consistBalancersLock sync.RWMutex - consistBalancerDaemonOnce sync.Once -) +var consistPickerPool sync.Pool func init() { consistPickerPool.New = newConsistPicker @@ -119,13 +111,9 @@ type realNode struct { type consistResult struct { Primary discovery.Instance Replicas []discovery.Instance - Touch atomic.Value } type consistInfo struct { - cachedConsistResult sync.Map - sfg singleflight.Group // To prevent multiple builds on the first request for the same key - realNodes []realNode virtualNodes []virtualNode } @@ -147,10 +135,10 @@ func (v *vNodeType) Swap(i, j int) { } type consistPicker struct { - cb *consistBalancer - info *consistInfo - index int - result *consistResult + cb *consistBalancer + info *consistInfo + // index int + // result *consistResult } func newConsistPicker() interface{} { @@ -160,8 +148,8 @@ func newConsistPicker() interface{} { func (cp *consistPicker) zero() { cp.info = nil cp.cb = nil - cp.index = 0 - cp.result = nil + // cp.index = 0 + // cp.result = nil } func (cp *consistPicker) Recycle() { @@ -174,43 +162,33 @@ func (cp *consistPicker) Next(ctx context.Context, request interface{}) discover if len(cp.info.realNodes) == 0 { return nil } - if cp.result == nil { - key := cp.cb.opt.GetKey(ctx, request) - if key == "" { - return nil - } - cp.result = cp.getConsistResult(xxhash3.HashString(key)) - cp.index = 0 - return cp.result.Primary - } - if cp.index < len(cp.result.Replicas) { - cp.index++ - return cp.result.Replicas[cp.index-1] - } - return nil -} - -func (cp *consistPicker) getConsistResult(key uint64) *consistResult { - var cr *consistResult - cri, ok := cp.info.cachedConsistResult.Load(key) - if !ok { - cri, _, _ = cp.info.sfg.Do(strconv.FormatUint(key, 10), func() (interface{}, error) { - cr := buildConsistResult(cp.cb, cp.info, key) - if cp.cb.opt.ExpireDuration > 0 { - cr.Touch.Store(time.Now()) - } - return cr, nil - }) - cp.info.cachedConsistResult.Store(key, cri) - } - cr = cri.(*consistResult) - if cp.cb.opt.ExpireDuration > 0 { - cr.Touch.Store(time.Now()) + key := cp.cb.opt.GetKey(ctx, request) + if key == "" { + return nil } - return cr + res := buildConsistResult(cp.info, xxhash3.HashString(key)) + return res.Primary + // Todo(DMwangnima): Optimise Replica-related logic + // This comment part is previous implementation considering connecting to Replica + // Since we would create a new picker each time, the Replica logic is unreachable, so just comment it out for now + + //if cp.result == nil { + // key := cp.cb.opt.GetKey(ctx, request) + // if key == "" { + // return nil + // } + // cp.result = buildConsistResult(cp.cb, cp.info, xxhash3.HashString(key)) + // //cp.index = 0 + // return cp.result.Primary + //} + //if cp.index < len(cp.result.Replicas) { + // cp.index++ + // return cp.result.Replicas[cp.index-1] + //} + //return nil } -func buildConsistResult(cb *consistBalancer, info *consistInfo, key uint64) *consistResult { +func buildConsistResult(info *consistInfo, key uint64) *consistResult { cr := &consistResult{} index := sort.Search(len(info.virtualNodes), func(i int) bool { return info.virtualNodes[i].hash > key @@ -220,42 +198,45 @@ func buildConsistResult(cb *consistBalancer, info *consistInfo, key uint64) *con index = 0 } cr.Primary = info.virtualNodes[index].RealNode.Ins - replicas := int(cb.opt.Replica) - // remove the primary node - if len(info.realNodes)-1 < replicas { - replicas = len(info.realNodes) - 1 - } - if replicas > 0 { - used := make(map[discovery.Instance]struct{}, replicas) // should be 1 + replicas - 1 - used[cr.Primary] = struct{}{} - cr.Replicas = make([]discovery.Instance, replicas) - for i := 0; i < replicas; i++ { - // find the next instance which is not used - // replicas are adjusted before so we can guarantee that we can find one - for { - index++ - if index == len(info.virtualNodes) { - index = 0 - } - ins := info.virtualNodes[index].RealNode.Ins - if _, ok := used[ins]; !ok { - used[ins] = struct{}{} - cr.Replicas[i] = ins - break - } - } - } - } return cr + // Todo(DMwangnima): Optimise Replica-related logic + // This comment part is previous implementation considering connecting to Replica + // Since we would create a new picker each time, the Replica logic is unreachable, so just comment it out + // for better performance + + //replicas := int(cb.opt.Replica) + //// remove the primary node + //if len(info.realNodes)-1 < replicas { + // replicas = len(info.realNodes) - 1 + //} + //if replicas > 0 { + // used := make(map[discovery.Instance]struct{}, replicas) // should be 1 + replicas - 1 + // used[cr.Primary] = struct{}{} + // cr.Replicas = make([]discovery.Instance, replicas) + // for i := 0; i < replicas; i++ { + // // find the next instance which is not used + // // replicas are adjusted before so we can guarantee that we can find one + // for { + // index++ + // if index == len(info.virtualNodes) { + // index = 0 + // } + // ins := info.virtualNodes[index].RealNode.Ins + // if _, ok := used[ins]; !ok { + // used[ins] = struct{}{} + // cr.Replicas[i] = ins + // break + // } + // } + // } + //} + //return cr } type consistBalancer struct { cachedConsistInfo sync.Map - // The main purpose of this lock is to improve performance and prevent Change from being performed while expire - // which may cause Change to do a lot of extra computation and memory allocation - updateLock sync.Mutex - opt ConsistentHashOption - sfg singleflight.Group + opt ConsistentHashOption + sfg singleflight.Group } // NewConsistBalancer creates a new consist balancer with the given option. @@ -269,47 +250,9 @@ func NewConsistBalancer(opt ConsistentHashOption) Loadbalancer { cb := &consistBalancer{ opt: opt, } - if cb.opt.ExpireDuration > 0 { - cb.AddToDaemon() - } return cb } -// AddToDaemon adds a balancer to the daemon expire routine. -func (cb *consistBalancer) AddToDaemon() { - // do delete func - consistBalancerDaemonOnce.Do(func() { - gofunc.GoFunc(context.Background(), func() { - for range time.Tick(2 * time.Minute) { - consistBalancersLock.RLock() - now := time.Now() - for _, lb := range consistBalancers { - if lb.opt.ExpireDuration > 0 { - lb.updateLock.Lock() - lb.cachedConsistInfo.Range(func(key, value interface{}) bool { - ci := value.(*consistInfo) - ci.cachedConsistResult.Range(func(key, value interface{}) bool { - t := value.(*consistResult).Touch.Load().(time.Time) - if now.After(t.Add(cb.opt.ExpireDuration)) { - ci.cachedConsistResult.Delete(key) - } - return true - }) - return true - }) - lb.updateLock.Unlock() - } - } - consistBalancersLock.RUnlock() - } - }) - }) - - consistBalancersLock.Lock() - consistBalancers = append(consistBalancers, cb) - consistBalancersLock.Unlock() -} - // GetPicker implements the Loadbalancer interface. func (cb *consistBalancer) GetPicker(e discovery.Result) Picker { var ci *consistInfo @@ -413,25 +356,6 @@ func (cb *consistBalancer) getVirtualNodeLen(rNode realNode) int { func (cb *consistBalancer) updateConsistInfo(e discovery.Result) { newInfo := cb.newConsistInfo(e) - infoI, loaded := cb.cachedConsistInfo.LoadOrStore(e.CacheKey, newInfo) - if !loaded { - return - } - info := infoI.(*consistInfo) - // Warm up. - // The reason for not modifying info directly is that there is no guarantee of concurrency security. - info.cachedConsistResult.Range(func(key, value interface{}) bool { - cr := buildConsistResult(cb, newInfo, key.(uint64)) - if cb.opt.ExpireDuration > 0 { - t := value.(*consistResult).Touch.Load().(time.Time) - if time.Now().After(t.Add(cb.opt.ExpireDuration)) { - return true - } - cr.Touch.Store(t) - } - newInfo.cachedConsistResult.Store(key, cr) - return true - }) cb.cachedConsistInfo.Store(e.CacheKey, newInfo) } @@ -442,9 +366,7 @@ func (cb *consistBalancer) Rebalance(change discovery.Change) { } // TODO: Use TreeMap to optimize performance when updating. // Now, due to the lack of a good red-black tree implementation, we can only build the full amount once per update. - cb.updateLock.Lock() cb.updateConsistInfo(change.Result) - cb.updateLock.Unlock() } // Delete implements the Rebalancer interface. @@ -454,9 +376,7 @@ func (cb *consistBalancer) Delete(change discovery.Change) { } // FIXME: If Delete and Rebalance occur together (Discovery OnDelete and OnChange are triggered at the same time), // it may cause the delete to fail and eventually lead to a resource leak. - cb.updateLock.Lock() cb.cachedConsistInfo.Delete(change.Result.CacheKey) - cb.updateLock.Unlock() } func (cb *consistBalancer) Name() string { diff --git a/pkg/loadbalance/consist_test.go b/pkg/loadbalance/consist_test.go index 1c97114db2..816583babe 100644 --- a/pkg/loadbalance/consist_test.go +++ b/pkg/loadbalance/consist_test.go @@ -19,8 +19,11 @@ package loadbalance import ( "context" "fmt" + "math/rand" "strconv" + "strings" "testing" + "time" "github.com/bytedance/gopkg/lang/fastrand" @@ -42,12 +45,37 @@ func getKey(ctx context.Context, request interface{}) string { return "1234" } +func getRandomKey(ctx context.Context, request interface{}) string { + key, ok := ctx.Value(keyCtxKey).(string) + if !ok { + return "" + } + return key +} + +func getRandomString(length int) string { + var resBuilder strings.Builder + resBuilder.Grow(length) + corpus := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + rand.Seed(time.Now().UnixNano() + int64(100)) + for i := 0; i < length; i++ { + resBuilder.WriteByte(corpus[rand.Intn(len(corpus))]) + } + return resBuilder.String() +} + func newTestConsistentHashOption() ConsistentHashOption { opt := NewConsistentHashOption(getKey) - opt.ExpireDuration = 0 return opt } +func TestNewConsistHashOption(t *testing.T) { + opt := NewConsistentHashOption(getKey) + test.Assert(t, opt.GetKey != nil) + test.Assert(t, opt.VirtualFactor == 100) + test.Assert(t, opt.Weighted) +} + func TestNewConsistBalancer(t *testing.T) { opt := ConsistentHashOption{} test.Panic(t, func() { NewConsistBalancer(opt) }) @@ -84,44 +112,45 @@ func TestConsistPicker_Next_Nil(t *testing.T) { test.Assert(t, cb.Name() == "consist") } -func TestConsistPicker_Replica(t *testing.T) { - opt := NewConsistentHashOption(getKey) - opt.Replica = 1 - opt.ExpireDuration = 0 - opt.GetKey = func(ctx context.Context, request interface{}) string { - return "1234" - } - insList := makeNInstances(2, 10) - e := discovery.Result{ - Cacheable: false, - CacheKey: "", - Instances: insList, - } - - cb := NewConsistBalancer(opt) - picker := cb.GetPicker(e) - first := picker.Next(context.TODO(), nil) - second := picker.Next(context.TODO(), nil) - test.Assert(t, first != second) -} - -func TestConsistPicker_Next_NoCache(t *testing.T) { - opt := newTestConsistentHashOption() - ins := discovery.NewInstance("tcp", "addr1", 10, nil) - insList := []discovery.Instance{ - ins, - } - e := discovery.Result{ - Cacheable: false, - CacheKey: "", - Instances: insList, - } - - cb := NewConsistBalancer(opt) - picker := cb.GetPicker(e) - test.Assert(t, picker.Next(context.TODO(), nil) == ins) - test.Assert(t, picker.Next(context.TODO(), nil) == nil) -} +// Replica related test +//func TestConsistPicker_Replica(t *testing.T) { +// opt := NewConsistentHashOption(getKey) +// opt.Replica = 1 +// opt.GetKey = func(ctx context.Context, request interface{}) string { +// return "1234" +// } +// insList := makeNInstances(2, 10) +// e := discovery.Result{ +// Cacheable: false, +// CacheKey: "", +// Instances: insList, +// } +// +// cb := NewConsistBalancer(opt) +// picker := cb.GetPicker(e) +// first := picker.Next(context.TODO(), nil) +// second := picker.Next(context.TODO(), nil) +// test.Assert(t, first != second) +//} + +// Replica related test +//func TestConsistPicker_Next_NoCache(t *testing.T) { +// opt := newTestConsistentHashOption() +// ins := discovery.NewInstance("tcp", "addr1", 10, nil) +// insList := []discovery.Instance{ +// ins, +// } +// e := discovery.Result{ +// Cacheable: false, +// CacheKey: "", +// Instances: insList, +// } +// +// cb := NewConsistBalancer(opt) +// picker := cb.GetPicker(e) +// test.Assert(t, picker.Next(context.TODO(), nil) == ins) +// test.Assert(t, picker.Next(context.TODO(), nil) == nil) +//} func TestConsistPicker_Next_NoCache_Consist(t *testing.T) { opt := newTestConsistentHashOption() @@ -209,10 +238,9 @@ func TestConsistBalance(t *testing.T) { GetKey: func(ctx context.Context, request interface{}) string { return strconv.Itoa(fastrand.Intn(100000)) }, - Replica: 0, - VirtualFactor: 1000, - Weighted: false, - ExpireDuration: 0, + Replica: 0, + VirtualFactor: 1000, + Weighted: false, } inss := makeNInstances(10, 10) @@ -239,10 +267,9 @@ func TestWeightedConsistBalance(t *testing.T) { GetKey: func(ctx context.Context, request interface{}) string { return strconv.Itoa(fastrand.Intn(100000)) }, - Replica: 0, - VirtualFactor: 1000, - Weighted: true, - ExpireDuration: 0, + Replica: 0, + VirtualFactor: 1000, + Weighted: true, } inss := makeNInstances(10, 10) @@ -351,3 +378,46 @@ func BenchmarkNewConsistPicker(bb *testing.B) { n *= 10 } } + +// BenchmarkConsistPicker_RandomDistributionKey +// BenchmarkConsistPicker_RandomDistributionKey/10ins +// BenchmarkConsistPicker_RandomDistributionKey/10ins-12 2417481 508.9 ns/op 48 B/op 1 allocs/op +// BenchmarkConsistPicker_RandomDistributionKey/100ins +// BenchmarkConsistPicker_RandomDistributionKey/100ins-12 2140726 534.6 ns/op 48 B/op 1 allocs/op +// BenchmarkConsistPicker_RandomDistributionKey/1000ins +// BenchmarkConsistPicker_RandomDistributionKey/1000ins-12 2848216. 407.7 ns/op 48 B/op 1 allocs/op +// BenchmarkConsistPicker_RandomDistributionKey/10000ins +// BenchmarkConsistPicker_RandomDistributionKey/10000ins-12 2701766 492.7 ns/op 48 B/op 1 allocs/op +func BenchmarkConsistPicker_RandomDistributionKey(bb *testing.B) { + n := 10 + balancer := NewConsistBalancer(NewConsistentHashOption(getRandomKey)) + + for i := 0; i < 4; i++ { + bb.Run(fmt.Sprintf("%dins", n), func(b *testing.B) { + inss := makeNInstances(n, 10) + e := discovery.Result{ + Cacheable: true, + CacheKey: "test", + Instances: inss, + } + picker := balancer.GetPicker(e) + ctx := context.WithValue(context.Background(), keyCtxKey, getRandomString(30)) + picker.Next(ctx, nil) + picker.(internal.Reusable).Recycle() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.Logf("round %d", i) + b.StopTimer() + ctx = context.WithValue(context.Background(), keyCtxKey, getRandomString(30)) + b.StartTimer() + picker := balancer.GetPicker(e) + picker.Next(ctx, nil) + if r, ok := picker.(internal.Reusable); ok { + r.Recycle() + } + } + }) + n *= 10 + } +} From c028c86797b962b91cd21676f7998004e4f0048c Mon Sep 17 00:00:00 2001 From: Scout Wang Date: Fri, 7 Jun 2024 20:57:57 +0800 Subject: [PATCH 45/49] fix: EnableSkipDecoder switch not working in Buffer Protocol scenario (#1378) --- pkg/remote/codec/thrift/skip_decoder.go | 4 - pkg/remote/codec/thrift/thrift.go | 7 +- pkg/remote/codec/thrift/thrift_frugal_test.go | 18 +++-- pkg/remote/codec/thrift/thrift_test.go | 79 ++++++++++++++++--- 4 files changed, 87 insertions(+), 21 deletions(-) diff --git a/pkg/remote/codec/thrift/skip_decoder.go b/pkg/remote/codec/thrift/skip_decoder.go index 1f3ca8d5fc..d209ae18a6 100644 --- a/pkg/remote/codec/thrift/skip_decoder.go +++ b/pkg/remote/codec/thrift/skip_decoder.go @@ -27,10 +27,6 @@ import ( "github.com/cloudwego/kitex/pkg/remote/codec/perrors" ) -const ( - EnableSkipDecoder CodecType = 0b10000 -) - // skipDecoder is used to parse the input byte-by-byte and skip the thrift payload // for making use of Frugal and FastCodec in standard Thrift Binary Protocol scenario. type skipDecoder struct { diff --git a/pkg/remote/codec/thrift/thrift.go b/pkg/remote/codec/thrift/thrift.go index eb9771e965..c6d2ca163e 100644 --- a/pkg/remote/codec/thrift/thrift.go +++ b/pkg/remote/codec/thrift/thrift.go @@ -41,7 +41,8 @@ const ( FastWrite CodecType = 0b0001 FastRead CodecType = 0b0010 - FastReadWrite = FastRead | FastWrite + FastReadWrite = FastRead | FastWrite + EnableSkipDecoder CodecType = 0b10000 ) var ( @@ -196,6 +197,10 @@ func (c thriftCodec) Unmarshal(ctx context.Context, message remote.Message, in r data := message.Data() msgBeginLen := bthrift.Binary.MessageBeginLength(methodName, msgType, seqID) dataLen := message.PayloadLen() - msgBeginLen - bthrift.Binary.MessageEndLength() + // For Buffer Protocol, dataLen would be negative. Set it to zero so as not to confuse + if dataLen < 0 { + dataLen = 0 + } ri := message.RPCInfo() rpcinfo.Record(ctx, ri, stats.WaitReadStart, nil) diff --git a/pkg/remote/codec/thrift/thrift_frugal_test.go b/pkg/remote/codec/thrift/thrift_frugal_test.go index f9417f69f9..1a17185ee1 100644 --- a/pkg/remote/codec/thrift/thrift_frugal_test.go +++ b/pkg/remote/codec/thrift/thrift_frugal_test.go @@ -123,13 +123,13 @@ func TestFrugalCodec(t *testing.T) { ctx := context.Background() codec := &thriftCodec{FrugalRead | FrugalWrite} - testFrugalDataConversion(t, ctx, codec) + testFrugalDataConversion(t, ctx, codec, transport.TTHeader) }) t.Run("fallback to frugal and data has tag", func(t *testing.T) { ctx := context.Background() codec := NewThriftCodec() - testFrugalDataConversion(t, ctx, codec) + testFrugalDataConversion(t, ctx, codec, transport.TTHeader) }) t.Run("configure BasicCodec to disable frugal fallback", func(t *testing.T) { ctx := context.Background() @@ -142,15 +142,21 @@ func TestFrugalCodec(t *testing.T) { out.Flush() test.Assert(t, err != nil) }) + t.Run("configure frugal and SkipDecoder for Buffer Protocol", func(t *testing.T) { + ctx := context.Background() + codec := NewThriftCodecWithConfig(FrugalRead | FrugalWrite | EnableSkipDecoder) + + testFrugalDataConversion(t, ctx, codec, transport.PurePayload) + }) }) } } -func testFrugalDataConversion(t *testing.T, ctx context.Context, codec remote.PayloadCodec) { +func testFrugalDataConversion(t *testing.T, ctx context.Context, codec remote.PayloadCodec, protocol transport.Protocol) { for _, tb := range transportBuffers { t.Run(tb.Name, func(t *testing.T) { // encode client side - sendMsg := initFrugalTagSendMsg(transport.TTHeader) + sendMsg := initFrugalTagSendMsg(protocol) buf := tb.NewBuffer() err := codec.Marshal(ctx, sendMsg, buf) test.Assert(t, err == nil, err) @@ -158,7 +164,9 @@ func testFrugalDataConversion(t *testing.T, ctx context.Context, codec remote.Pa // decode server side recvMsg := initFrugalTagRecvMsg() - recvMsg.SetPayloadLen(buf.ReadableLen()) + if protocol != transport.PurePayload { + recvMsg.SetPayloadLen(buf.ReadableLen()) + } test.Assert(t, err == nil, err) err = codec.Unmarshal(ctx, recvMsg, buf) test.Assert(t, err == nil, err) diff --git a/pkg/remote/codec/thrift/thrift_test.go b/pkg/remote/codec/thrift/thrift_test.go index 3d4029b881..14372ce6e0 100644 --- a/pkg/remote/codec/thrift/thrift_test.go +++ b/pkg/remote/codec/thrift/thrift_test.go @@ -131,17 +131,7 @@ func TestNormal(t *testing.T) { test.Assert(t, err == nil, err) // compare Req Arg - sendReq := (sendMsg.Data()).(*mt.MockTestArgs).Req - recvReq := (recvMsg.Data()).(*mt.MockTestArgs).Req - test.Assert(t, sendReq.Msg == recvReq.Msg) - test.Assert(t, len(sendReq.StrList) == len(recvReq.StrList)) - test.Assert(t, len(sendReq.StrMap) == len(recvReq.StrMap)) - for i, item := range sendReq.StrList { - test.Assert(t, item == recvReq.StrList[i]) - } - for k := range sendReq.StrMap { - test.Assert(t, sendReq.StrMap[k] == recvReq.StrMap[k]) - } + compare(t, sendMsg, recvMsg) }) } } @@ -229,6 +219,59 @@ func TestTransErrorUnwrap(t *testing.T) { test.Assert(t, uwErr2.Error() == errMsg) } +func TestSkipDecoder(t *testing.T) { + testcases := []struct { + desc string + codec remote.PayloadCodec + protocol transport.Protocol + }{ + { + desc: "Disable SkipDecoder, fallback to Apache Thrift Codec for Buffer Protocol", + codec: NewThriftCodec(), + protocol: transport.PurePayload, + }, + { + desc: "Disable SkipDecoder, using FastCodec for TTHeader Protocol", + codec: NewThriftCodec(), + protocol: transport.TTHeader, + }, + { + desc: "Enable SkipDecoder, using FastCodec for Buffer Protocol", + codec: NewThriftCodecWithConfig(FastRead | FastWrite | EnableSkipDecoder), + protocol: transport.PurePayload, + }, + { + desc: "Enable SkipDecoder, using FastCodec for TTHeader Protocol", + codec: NewThriftCodecWithConfig(FastRead | FastWrite | EnableSkipDecoder), + protocol: transport.TTHeader, + }, + } + + for _, tc := range testcases { + for _, tb := range transportBuffers { + t.Run(tc.desc+"#"+tb.Name, func(t *testing.T) { + // encode client side + sendMsg := initSendMsg(tc.protocol) + buf := tb.NewBuffer() + err := tc.codec.Marshal(context.Background(), sendMsg, buf) + test.Assert(t, err == nil, err) + buf.Flush() + + // decode server side + recvMsg := initRecvMsg() + if tc.protocol != transport.PurePayload { + recvMsg.SetPayloadLen(buf.ReadableLen()) + } + err = tc.codec.Unmarshal(context.Background(), recvMsg, buf) + test.Assert(t, err == nil, err) + + // compare Req Arg + compare(t, sendMsg, recvMsg) + }) + } + } +} + func initSendMsg(tp transport.Protocol) remote.Message { var _args mt.MockTestArgs _args.Req = prepareReq() @@ -247,6 +290,20 @@ func initRecvMsg() remote.Message { return msg } +func compare(t *testing.T, sendMsg, recvMsg remote.Message) { + sendReq := (sendMsg.Data()).(*mt.MockTestArgs).Req + recvReq := (recvMsg.Data()).(*mt.MockTestArgs).Req + test.Assert(t, sendReq.Msg == recvReq.Msg) + test.Assert(t, len(sendReq.StrList) == len(recvReq.StrList)) + test.Assert(t, len(sendReq.StrMap) == len(recvReq.StrMap)) + for i, item := range sendReq.StrList { + test.Assert(t, item == recvReq.StrList[i]) + } + for k := range sendReq.StrMap { + test.Assert(t, sendReq.StrMap[k] == recvReq.StrMap[k]) + } +} + func initServerErrorMsg(tp transport.Protocol, ri rpcinfo.RPCInfo, transErr *remote.TransError) remote.Message { errMsg := remote.NewMessage(transErr, svcInfo, ri, remote.Exception, remote.Server) errMsg.SetProtocolInfo(remote.NewProtocolInfo(tp, svcInfo.PayloadCodec)) From 5982d738126eae95b87537bd3319044efdd729ea Mon Sep 17 00:00:00 2001 From: Marina Sakai <118230951+Marina-Sakai@users.noreply.github.com> Date: Fri, 7 Jun 2024 21:44:47 +0800 Subject: [PATCH 46/49] fix: fix a bug "unknown service xxx" when using generic client by not writing IDLServiceName when it's generic service (#1379) --- pkg/transmeta/ttheader.go | 10 ++++++++-- pkg/transmeta/ttheader_test.go | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/transmeta/ttheader.go b/pkg/transmeta/ttheader.go index 801ba3f025..def6b421f5 100644 --- a/pkg/transmeta/ttheader.go +++ b/pkg/transmeta/ttheader.go @@ -26,6 +26,7 @@ import ( "github.com/cloudwego/kitex/pkg/remote" "github.com/cloudwego/kitex/pkg/remote/transmeta" "github.com/cloudwego/kitex/pkg/rpcinfo" + "github.com/cloudwego/kitex/pkg/serviceinfo" "github.com/cloudwego/kitex/pkg/utils" "github.com/cloudwego/kitex/transport" ) @@ -74,9 +75,14 @@ func (ch *clientTTHeaderHandler) WriteMeta(ctx context.Context, msg remote.Messa if cfg.IsRPCTimeoutLocked() { hd[transmeta.RPCTimeout] = strconv.Itoa(int(ri.Config().RPCTimeout().Milliseconds())) } - + if cfg.IsConnectTimeoutLocked() { + hd[transmeta.ConnectTimeout] = strconv.Itoa(int(ri.Config().ConnectTimeout().Milliseconds())) + } transInfo.PutTransIntInfo(hd) - transInfo.PutTransStrInfo(map[string]string{transmeta.HeaderIDLServiceName: ri.Invocation().ServiceName()}) + idlSvcName := ri.Invocation().ServiceName() + if idlSvcName != serviceinfo.GenericService { + transInfo.PutTransStrInfo(map[string]string{transmeta.HeaderIDLServiceName: idlSvcName}) + } return ctx, nil } diff --git a/pkg/transmeta/ttheader_test.go b/pkg/transmeta/ttheader_test.go index c01963e4dd..1bf0453534 100644 --- a/pkg/transmeta/ttheader_test.go +++ b/pkg/transmeta/ttheader_test.go @@ -52,7 +52,9 @@ func TestTTHeaderClientWriteMetainfo(t *testing.T) { cfg := rpcinfo.NewRPCConfig() cfgMutable := rpcinfo.AsMutableRPCConfig(cfg) cfgMutable.SetRPCTimeout(time.Millisecond * 100) + cfgMutable.SetConnectTimeout(time.Millisecond * 1000) cfgMutable.LockConfig(rpcinfo.BitRPCTimeout) + cfgMutable.LockConfig(rpcinfo.BitConnectTimeout) fromInfo := rpcinfo.NewEndpointInfo("fromServiceName", "fromMethod", nil, nil) toInfo := rpcinfo.NewEndpointInfo("toServiceName", "toMethod", nil, nil) @@ -81,6 +83,7 @@ func TestTTHeaderClientWriteMetainfo(t *testing.T) { test.Assert(t, kvs[transmeta.MsgType] == strconv.Itoa(int(remote.Call))) test.Assert(t, kvs[transmeta.TransportType] == unframedTransportType) test.Assert(t, kvs[transmeta.RPCTimeout] == "100") + test.Assert(t, kvs[transmeta.ConnectTimeout] == "1000") strKvs = msg.TransInfo().TransStrInfo() test.Assert(t, len(strKvs) == 1) test.Assert(t, strKvs[transmeta.HeaderIDLServiceName] == "") From 8750d35b55521451cecee31739e4b8f35e42cd0d Mon Sep 17 00:00:00 2001 From: QihengZhou Date: Wed, 12 Jun 2024 11:28:08 +0800 Subject: [PATCH 47/49] chore: upgrade netpoll to v0.6.1 (#1383) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c6a56f5476..287b011f1b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/cloudwego/fastpb v0.0.4 github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 - github.com/cloudwego/netpoll v0.6.1-0.20240516030022-a9a224c3e494 + github.com/cloudwego/netpoll v0.6.1 github.com/cloudwego/runtimex v0.1.0 github.com/cloudwego/thriftgo v0.3.6 github.com/golang/mock v1.6.0 diff --git a/go.sum b/go.sum index 00ae3d5633..9b6b436062 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/cloudwego/kitex v0.9.3-rc/go.mod h1:QurmwA8Wh/s7qz6C+Da9sc9B4TRW6q4TN github.com/cloudwego/localsession v0.0.2 h1:N9/IDtCPj1fCL9bCTP+DbXx3f40YjVYWcwkJG0YhQkY= github.com/cloudwego/localsession v0.0.2/go.mod h1:kiJxmvAcy4PLgKtEnPS5AXed3xCiXcs7Z+KBHP72Wv8= github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= -github.com/cloudwego/netpoll v0.6.1-0.20240516030022-a9a224c3e494 h1:o43pLtNAhtgucplmqwPNHMNTJEmkJ9bcZKpx9dBCu4Y= -github.com/cloudwego/netpoll v0.6.1-0.20240516030022-a9a224c3e494/go.mod h1:kaqvfZ70qd4T2WtIIpCOi5Cxyob8viEpzLhCrTrz3HM= +github.com/cloudwego/netpoll v0.6.1 h1:Cjftvi6bmumsOijmuUFy6HqAUXMxAT3fKK96wsrm3XA= +github.com/cloudwego/netpoll v0.6.1/go.mod h1:kaqvfZ70qd4T2WtIIpCOi5Cxyob8viEpzLhCrTrz3HM= github.com/cloudwego/runtimex v0.1.0 h1:HG+WxWoj5/CDChDZ7D99ROwvSMkuNXAqt6hnhTTZDiI= github.com/cloudwego/runtimex v0.1.0/go.mod h1:23vL/HGV0W8nSCHbe084AgEBdDV4rvXenEUMnUNvUd8= github.com/cloudwego/thriftgo v0.2.11/go.mod h1:dAyXHEmKXo0LfMCrblVEY3mUZsdeuA5+i0vF5f09j7E= From 1cc0c2c099e7951da80d8760b9257d5bd901f900 Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Wed, 12 Jun 2024 13:21:17 +0800 Subject: [PATCH 48/49] chore: update dynamicgo to v0.2.8 (#1385) --- go.mod | 4 ++-- go.sum | 22 ++++++---------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 287b011f1b..4852558c42 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ require ( github.com/apache/thrift v0.13.0 github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1 github.com/bytedance/mockey v1.2.7 - github.com/bytedance/sonic v1.11.6 + github.com/bytedance/sonic v1.11.8 github.com/cloudwego/configmanager v0.2.2 - github.com/cloudwego/dynamicgo v0.2.4 + github.com/cloudwego/dynamicgo v0.2.8 github.com/cloudwego/fastpb v0.0.4 github.com/cloudwego/frugal v0.1.15 github.com/cloudwego/localsession v0.0.2 diff --git a/go.sum b/go.sum index 9b6b436062..a79cac681c 100644 --- a/go.sum +++ b/go.sum @@ -12,35 +12,30 @@ github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/gopkg v0.0.0-20240507064146-197ded923ae3/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1 h1:rT7Mm6uUpHeZQzfs2v0Mlj0SL02CzyVi+EB7VYPM/z4= github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= github.com/bytedance/mockey v1.2.7 h1:8j4yCqS5OmMe2dQCxPit4FVkwTK9nrykIgbOZN3s28o= github.com/bytedance/mockey v1.2.7/go.mod h1:bNrUnI1u7+pAc0TYDgPATM+wF2yzHxmNH+iDXg4AOCU= -github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY= +github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA= +github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/choleraehyq/pid v0.0.18/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1umPV+aN8pi8= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/configmanager v0.2.1/go.mod h1:0oD/BWaTuznBOawVeTmTl3LE99RWaw7rX2jECowmY58= github.com/cloudwego/configmanager v0.2.2 h1:sVrJB8gWYTlPV2OS3wcgJSO9F2/9Zbkmcm1Z7jempOU= github.com/cloudwego/configmanager v0.2.2/go.mod h1:ppiyU+5TPLonE8qMVi/pFQk2eL3Q4P7d4hbiNJn6jwI= -github.com/cloudwego/dynamicgo v0.2.2-dep3/go.mod h1:oFLzd9SEUtU7XbSc7AT9e5xoAV1OJ1mVpudtUOiD7PQ= -github.com/cloudwego/dynamicgo v0.2.2/go.mod h1:k840iCFH9ng9PBqr6jIoOyZxdk58EPEccrbfOk4ni1s= -github.com/cloudwego/dynamicgo v0.2.4 h1:xdBezj2ziiKSir0+vdvFMWi8AESKdA1JPIuuB12LCU4= -github.com/cloudwego/dynamicgo v0.2.4/go.mod h1:BXXaLtNH/nNIZi5HsE8lupiMKPmTogJ8z+KGFEySqUg= +github.com/cloudwego/dynamicgo v0.2.7 h1:D6vAUqGC00kj5fOme/ipftyH38/Af+8kmczLFs9z2SM= +github.com/cloudwego/dynamicgo v0.2.7/go.mod h1:F3jlbPmlNzhcuDMXwZoBJ7rJKpg2iE+TnIy9pSJiGzs= +github.com/cloudwego/dynamicgo v0.2.8 h1:rCkVEZzXpdnM1dlunu6mMz5pDtZfBQFv9/FAk1hOoOE= +github.com/cloudwego/dynamicgo v0.2.8/go.mod h1:F3jlbPmlNzhcuDMXwZoBJ7rJKpg2iE+TnIy9pSJiGzs= github.com/cloudwego/fastpb v0.0.4 h1:/ROVVfoFtpfc+1pkQLzGs+azjxUbSOsAqSY4tAAx4mg= github.com/cloudwego/fastpb v0.0.4/go.mod h1:/V13XFTq2TUkxj2qWReV8MwfPC4NnPcy6FsrojnsSG0= github.com/cloudwego/frugal v0.1.15 h1:LC55UJKhQPMFVjDPbE+LJcF7etZjSx6uokG1tk0wPK0= @@ -48,16 +43,12 @@ github.com/cloudwego/frugal v0.1.15/go.mod h1:26kU1r18vA8vRg12c66XPDlfv1GQHDbE1R github.com/cloudwego/iasm v0.0.9/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/cloudwego/kitex v0.9.3-dep3/go.mod h1:87pfgsZMaso0tFISQZzMhwmh6pajfVmw/R3COsh2pCg= -github.com/cloudwego/kitex v0.9.3-rc/go.mod h1:QurmwA8Wh/s7qz6C+Da9sc9B4TRW6q4TN6Y56mu90SE= github.com/cloudwego/localsession v0.0.2 h1:N9/IDtCPj1fCL9bCTP+DbXx3f40YjVYWcwkJG0YhQkY= github.com/cloudwego/localsession v0.0.2/go.mod h1:kiJxmvAcy4PLgKtEnPS5AXed3xCiXcs7Z+KBHP72Wv8= -github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= github.com/cloudwego/netpoll v0.6.1 h1:Cjftvi6bmumsOijmuUFy6HqAUXMxAT3fKK96wsrm3XA= github.com/cloudwego/netpoll v0.6.1/go.mod h1:kaqvfZ70qd4T2WtIIpCOi5Cxyob8viEpzLhCrTrz3HM= github.com/cloudwego/runtimex v0.1.0 h1:HG+WxWoj5/CDChDZ7D99ROwvSMkuNXAqt6hnhTTZDiI= github.com/cloudwego/runtimex v0.1.0/go.mod h1:23vL/HGV0W8nSCHbe084AgEBdDV4rvXenEUMnUNvUd8= -github.com/cloudwego/thriftgo v0.2.11/go.mod h1:dAyXHEmKXo0LfMCrblVEY3mUZsdeuA5+i0vF5f09j7E= github.com/cloudwego/thriftgo v0.3.6 h1:gHHW8Ag3cAEQ/awP4emTJiRPr5yQjbANhcsmV8/Epbw= github.com/cloudwego/thriftgo v0.3.6/go.mod h1:29ukiySoAMd0vXMYIduAY9dph/7dmChvOS11YLotFb8= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -281,7 +272,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 15964cb49b47f601cbfe2b94d51722b7751aa66b Mon Sep 17 00:00:00 2001 From: alice <90381261+alice-yyds@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:24:53 +0800 Subject: [PATCH 49/49] chore: update version v0.10.0 --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 5f812bb639..a61ab0bf67 100644 --- a/version.go +++ b/version.go @@ -19,5 +19,5 @@ package kitex // Name and Version info of this framework, used for statistics and debug const ( Name = "Kitex" - Version = "v0.9.1" + Version = "v0.10.0" )