diff --git a/.gitignore b/.gitignore index 0b3673bf316a409b5caadf09f3fa58d42ad8f968..f189060bb351db399efe009a676ac3984b634215 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ vendor/ logs/ .vscode/ +coverage.txt diff --git a/cluster/cluster_impl/failback_cluster_invoker.go b/cluster/cluster_impl/failback_cluster_invoker.go index 07792de0fd75333ac689e14e4e208b7386392309..d7d01dd5c58662940b297cdb9b8a36a78e897d8f 100644 --- a/cluster/cluster_impl/failback_cluster_invoker.go +++ b/cluster/cluster_impl/failback_cluster_invoker.go @@ -18,31 +18,37 @@ package cluster_impl import ( - "container/list" - perrors "github.com/pkg/errors" "sync" "time" ) +import ( + "github.com/Workiva/go-datastructures/queue" +) + import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" ) +/** + * When fails, record failure requests and schedule for retry on a regular interval. + * Especially useful for services of notification. + * + * <a href="http://en.wikipedia.org/wiki/Failback">Failback</a> + */ type failbackClusterInvoker struct { baseClusterInvoker -} -var ( - retries int64 - failbackTasks int64 - ticker *time.Ticker once sync.Once - lock sync.Mutex - taskList *Queue -) + ticker *time.Ticker + maxRetries int64 + failbackTasks int64 + taskList *queue.Queue +} func newFailbackClusterInvoker(directory cluster.Directory) protocol.Invoker { invoker := &failbackClusterInvoker{ @@ -56,19 +62,71 @@ func newFailbackClusterInvoker(directory cluster.Directory) protocol.Invoker { if failbackTasksConfig <= 0 { failbackTasksConfig = constant.DEFAULT_FAILBACK_TASKS } - retries = retriesConfig - failbackTasks = failbackTasksConfig + invoker.maxRetries = retriesConfig + invoker.failbackTasks = failbackTasksConfig return invoker } -func (invoker *failbackClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *failbackClusterInvoker) process() { + invoker.ticker = time.NewTicker(time.Second * 1) + for range invoker.ticker.C { + // check each timeout task and re-run + for { + value, err := invoker.taskList.Peek() + if err == queue.ErrDisposed { + return + } + if err == queue.ErrEmptyQueue { + break + } + + retryTask := value.(*retryTimerTask) + if time.Since(retryTask.lastT).Seconds() < 5 { + break + } + + // ignore return. the get must success. + _, err = invoker.taskList.Get(1) + if err != nil { + logger.Warnf("get task found err: %v\n", err) + break + } + + go func(retryTask *retryTimerTask) { + invoked := make([]protocol.Invoker, 0) + invoked = append(invoked, retryTask.lastInvoker) + + retryInvoker := invoker.doSelect(retryTask.loadbalance, retryTask.invocation, retryTask.invokers, invoked) + var result protocol.Result + result = retryInvoker.Invoke(retryTask.invocation) + if result.Error() != nil { + retryTask.lastInvoker = retryInvoker + invoker.checkRetry(retryTask, result.Error()) + } + }(retryTask) + } + } +} + +func (invoker *failbackClusterInvoker) checkRetry(retryTask *retryTimerTask, err error) { + logger.Errorf("Failed retry to invoke the method %v in the service %v, wait again. The exception: %v.\n", + retryTask.invocation.MethodName(), invoker.GetUrl().Service(), err.Error()) + retryTask.retries++ + retryTask.lastT = time.Now() + if retryTask.retries > invoker.maxRetries { + logger.Errorf("Failed retry times exceed threshold (%v), We have to abandon, invocation-> %v.\n", + retryTask.retries, retryTask.invocation) + } else { + invoker.taskList.Put(retryTask) + } +} + +func (invoker *failbackClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) - if err != nil { - // add retry ticker task - perrors.Errorf("Failed to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.", + logger.Errorf("Failed to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.\n", invocation.MethodName(), invoker.GetUrl().Service(), err) return &protocol.RPCResult{} } @@ -84,19 +142,30 @@ func (invoker *failbackClusterInvoker) Invoke(invocation protocol.Invocation) pr } loadbalance := extension.GetLoadbalance(lb) - invoked := []protocol.Invoker{} + invoked := make([]protocol.Invoker, 0) var result protocol.Result ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked) invoked = append(invoked, ivk) //DO INVOKE result = ivk.Invoke(invocation) - if result.Error() != nil { - // add retry ticker task - addFailed(loadbalance, invocation, invokers, invoker) - perrors.Errorf("Failback to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.", - methodName, invoker.GetUrl().Service(), result.Error().Error()) + invoker.once.Do(func() { + invoker.taskList = queue.New(invoker.failbackTasks) + go invoker.process() + }) + + taskLen := invoker.taskList.Len() + if taskLen >= invoker.failbackTasks { + logger.Warnf("tasklist is too full > %d.\n", taskLen) + return &protocol.RPCResult{} + } + + timerTask := newRetryTimerTask(loadbalance, invocation, invokers, ivk) + invoker.taskList.Put(timerTask) + + logger.Errorf("Failback to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.\n", + methodName, url.Service(), result.Error().Error()) // ignore return &protocol.RPCResult{} } @@ -105,102 +174,30 @@ func (invoker *failbackClusterInvoker) Invoke(invocation protocol.Invocation) pr } func (invoker *failbackClusterInvoker) Destroy() { - //this is must atom operation - if invoker.destroyed.CAS(false, true) { - invoker.directory.Destroy() - } - // stop ticker - ticker.Stop() -} - -func addFailed(balance cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, - invoker *failbackClusterInvoker) { - initSingleTickerTaskInstance() - // init one retryTimerTask - timerTask := newRetryTimerTask(balance, invocation, invokers, invoker, retries, 5) - taskList.push(timerTask) - // process ticker task - go func() { - <-ticker.C - value := taskList.pop() - if value == nil { - return - } - - retryTask := value.(retryTimerTask) - invoked := []protocol.Invoker{} - invoked = append(invoked, retryTask.lastInvoker) - retryInvoker := invoker.doSelect(retryTask.loadbalance, retryTask.invocation, retryTask.invokers, - invoked) - var result protocol.Result - result = retryInvoker.Invoke(retryTask.invocation) - if result.Error() != nil { - perrors.Errorf("Failed retry to invoke the method %v in the service %v, wait again. The exception: %v.", - invocation.MethodName(), invoker.GetUrl().Service(), result.Error().Error()) - retryTask.retries++ - if retryTask.retries > retries { - perrors.Errorf("Failed retry times exceed threshold (%v), We have to abandon, invocation-> %v", - retries, invocation) - } else { - taskList.push(retryTask) - } - } - }() -} + invoker.baseClusterInvoker.Destroy() -func initSingleTickerTaskInstance() { - once.Do(func() { - newTickerTask() - }) -} + // stop ticker + invoker.ticker.Stop() -func newTickerTask() { - ticker = time.NewTicker(time.Second * 1) - taskList = newQueue() + _ = invoker.taskList.Dispose() } type retryTimerTask struct { loadbalance cluster.LoadBalance invocation protocol.Invocation invokers []protocol.Invoker - lastInvoker *failbackClusterInvoker + lastInvoker protocol.Invoker retries int64 - tick int64 + lastT time.Time } func newRetryTimerTask(loadbalance cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, - lastInvoker *failbackClusterInvoker, retries int64, tick int64) *retryTimerTask { + lastInvoker protocol.Invoker) *retryTimerTask { return &retryTimerTask{ loadbalance: loadbalance, invocation: invocation, invokers: invokers, lastInvoker: lastInvoker, - retries: retries, - tick: tick, + lastT: time.Now(), } } - -type Queue struct { - data *list.List -} - -func newQueue() *Queue { - q := new(Queue) - q.data = list.New() - return q -} - -func (q *Queue) push(v interface{}) { - defer lock.Unlock() - lock.Lock() - q.data.PushFront(v) -} - -func (q *Queue) pop() interface{} { - defer lock.Unlock() - lock.Lock() - iter := q.data.Back() - v := iter.Value - q.data.Remove(iter) - return v -} diff --git a/cluster/cluster_impl/failback_cluster_test.go b/cluster/cluster_impl/failback_cluster_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2cdd14e5636a8d7db09f9c47653e5de745193863 --- /dev/null +++ b/cluster/cluster_impl/failback_cluster_test.go @@ -0,0 +1,242 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 cluster_impl + +import ( + "context" + "sync" + "testing" + "time" +) + +import ( + "github.com/golang/mock/gomock" + perrors "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/directory" + "github.com/apache/dubbo-go/cluster/loadbalance" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/mock" +) + +var ( + failbackUrl, _ = common.NewURL(context.TODO(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") +) + +// registerFailback register failbackCluster to cluster extension. +func registerFailback(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { + extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) + failbackCluster := NewFailbackCluster() + + invokers := []protocol.Invoker{} + invokers = append(invokers, invoker) + + invoker.EXPECT().GetUrl().Return(failbackUrl) + + staticDir := directory.NewStaticDirectory(invokers) + clusterInvoker := failbackCluster.Join(staticDir) + return clusterInvoker +} + +// success firstly, failback should return origin invoke result. +func Test_FailbackSuceess(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + invoker := mock.NewMockInvoker(ctrl) + clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + + invoker.EXPECT().GetUrl().Return(failbackUrl).Times(1) + + mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} + invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) + + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + assert.Equal(t, mockResult, result) +} + +// failed firstly, success later after one retry. +func Test_FailbackRetryOneSuccess(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + invoker := mock.NewMockInvoker(ctrl) + clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + + invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() + + // failed at first + mockFailedResult := &protocol.RPCResult{Err: perrors.New("error")} + invoker.EXPECT().Invoke(gomock.Any()).Return(mockFailedResult) + + // success second + var wg sync.WaitGroup + wg.Add(1) + now := time.Now() + mockSuccResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result { + delta := time.Since(now).Nanoseconds() / int64(time.Second) + assert.True(t, delta >= 5) + wg.Done() + return mockSuccResult + }) + + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + assert.Nil(t, result.Error()) + assert.Nil(t, result.Result()) + assert.Equal(t, 0, len(result.Attachments())) + + // ensure the retry task has been executed + assert.Equal(t, int64(1), clusterInvoker.taskList.Len()) + // wait until the retry task is executed, the taskList will be empty. + wg.Wait() + assert.Equal(t, int64(0), clusterInvoker.taskList.Len()) + + invoker.EXPECT().Destroy().Return() + clusterInvoker.Destroy() + + assert.Equal(t, int64(0), clusterInvoker.taskList.Len()) +} + +// failed firstly, and failed again after ech retry time. +func Test_FailbackRetryFailed(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + invoker := mock.NewMockInvoker(ctrl) + clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + + invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() + + mockFailedResult := &protocol.RPCResult{Err: perrors.New("error")} + invoker.EXPECT().Invoke(gomock.Any()).Return(mockFailedResult) + + // + var wg sync.WaitGroup + retries := 2 + wg.Add(retries) + now := time.Now() + + // add retry call that eventually failed. + for i := 0; i < retries; i++ { + j := i + 1 + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result { + delta := time.Since(now).Nanoseconds() / int64(time.Second) + assert.True(t, delta >= int64(5*j)) + wg.Done() + return mockFailedResult + }) + } + + // first call should failed. + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + assert.Nil(t, result.Error()) + assert.Nil(t, result.Result()) + assert.Equal(t, 0, len(result.Attachments())) + + wg.Wait() + time.Sleep(time.Second) + assert.Equal(t, int64(1), clusterInvoker.taskList.Len()) + + invoker.EXPECT().Destroy().Return() + clusterInvoker.Destroy() + // after destory, the tasklist will be empty + assert.Equal(t, int64(0), clusterInvoker.taskList.Len()) +} + +// add 10 tasks but all failed firstly, and failed again with one retry. +func Test_FailbackRetryFailed10Times(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + invoker := mock.NewMockInvoker(ctrl) + clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + clusterInvoker.maxRetries = 10 + + invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() + + // 10 task should failed firstly. + mockFailedResult := &protocol.RPCResult{Err: perrors.New("error")} + invoker.EXPECT().Invoke(gomock.Any()).Return(mockFailedResult).Times(10) + + // 10 task should retry and failed. + var wg sync.WaitGroup + wg.Add(10) + now := time.Now() + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result { + delta := time.Since(now).Nanoseconds() / int64(time.Second) + assert.True(t, delta >= 5) + wg.Done() + return mockFailedResult + }).Times(10) + + for i := 0; i < 10; i++ { + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + assert.Nil(t, result.Error()) + assert.Nil(t, result.Result()) + assert.Equal(t, 0, len(result.Attachments())) + } + + wg.Wait() + time.Sleep(time.Second) // in order to ensure checkRetry have done + assert.Equal(t, int64(10), clusterInvoker.taskList.Len()) + + invoker.EXPECT().Destroy().Return() + clusterInvoker.Destroy() + + assert.Equal(t, int64(0), clusterInvoker.taskList.Len()) +} + +func Test_FailbackOutOfLimit(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + invoker := mock.NewMockInvoker(ctrl) + clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + clusterInvoker.failbackTasks = 1 + + invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() + + mockFailedResult := &protocol.RPCResult{Err: perrors.New("error")} + invoker.EXPECT().Invoke(gomock.Any()).Return(mockFailedResult).Times(11) + + // reached limit + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + assert.Nil(t, result.Error()) + assert.Nil(t, result.Result()) + assert.Equal(t, 0, len(result.Attachments())) + + // all will be out of limit + for i := 0; i < 10; i++ { + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + assert.Nil(t, result.Error()) + assert.Nil(t, result.Result()) + assert.Equal(t, 0, len(result.Attachments())) + + assert.Equal(t, int64(1), clusterInvoker.taskList.Len()) + } + + invoker.EXPECT().Destroy().Return() + clusterInvoker.Destroy() +} diff --git a/cluster/cluster_impl/failsafe_cluster_invoker.go b/cluster/cluster_impl/failsafe_cluster_invoker.go index a006e918a2740381858a6f998529f40852ea443f..0a1da1bc49bfe01ffc20be2f4941ef752d026378 100644 --- a/cluster/cluster_impl/failsafe_cluster_invoker.go +++ b/cluster/cluster_impl/failsafe_cluster_invoker.go @@ -17,17 +17,21 @@ package cluster_impl -import ( - perrors "github.com/pkg/errors" -) - import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" ) +/** + * When invoke fails, log the error message and ignore this error by returning an empty Result. + * Usually used to write audit logs and other operations + * + * <a href="http://en.wikipedia.org/wiki/Fail-safe">Fail-safe</a> + * + */ type failsafeClusterInvoker struct { baseClusterInvoker } @@ -39,28 +43,24 @@ func newFailsafeClusterInvoker(directory cluster.Directory) protocol.Invoker { } func (invoker *failsafeClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { - invokers := invoker.directory.List(invocation) - err := invoker.checkInvokers(invokers, invocation) + err := invoker.checkInvokers(invokers, invocation) if err != nil { return &protocol.RPCResult{} } url := invokers[0].GetUrl() - methodName := invocation.MethodName() //Get the service loadbalance config lb := url.GetParam(constant.LOADBALANCE_KEY, constant.DEFAULT_LOADBALANCE) - //Get the service method loadbalance config if have if v := url.GetMethodParam(methodName, constant.LOADBALANCE_KEY, ""); v != "" { lb = v } loadbalance := extension.GetLoadbalance(lb) - invoked := []protocol.Invoker{} - + invoked := make([]protocol.Invoker, 0) var result protocol.Result ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked) @@ -69,9 +69,8 @@ func (invoker *failsafeClusterInvoker) Invoke(invocation protocol.Invocation) pr result = ivk.Invoke(invocation) if result.Error() != nil { // ignore - perrors.Errorf("Failsafe ignore exception: %v.", result.Error().Error()) + logger.Errorf("Failsafe ignore exception: %v.\n", result.Error().Error()) return &protocol.RPCResult{} } return result - } diff --git a/cluster/cluster_impl/failsafe_cluster_test.go b/cluster/cluster_impl/failsafe_cluster_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9ee9d9fee31b0cb24d877ab3dc0e24fb552f5f11 --- /dev/null +++ b/cluster/cluster_impl/failsafe_cluster_test.go @@ -0,0 +1,95 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 cluster_impl + +import ( + "context" + "testing" +) + +import ( + "github.com/golang/mock/gomock" + perrors "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/directory" + "github.com/apache/dubbo-go/cluster/loadbalance" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/mock" +) + +var ( + failsafeUrl, _ = common.NewURL(context.TODO(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") +) + +// register_failsafe register failsafeCluster to cluster extension. +func register_failsafe(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { + extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) + failsafeCluster := NewFailsafeCluster() + + invokers := []protocol.Invoker{} + invokers = append(invokers, invoker) + + invoker.EXPECT().GetUrl().Return(failbackUrl) + + staticDir := directory.NewStaticDirectory(invokers) + clusterInvoker := failsafeCluster.Join(staticDir) + return clusterInvoker +} + +func Test_FailSafeInvokeSuccess(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + invoker := mock.NewMockInvoker(ctrl) + clusterInvoker := register_failsafe(t, invoker) + + invoker.EXPECT().GetUrl().Return(failsafeUrl) + + mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} + + invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + + assert.NoError(t, result.Error()) + res := result.Result().(rest) + assert.True(t, res.success) +} + +func Test_FailSafeInvokeFail(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + invoker := mock.NewMockInvoker(ctrl) + clusterInvoker := register_failsafe(t, invoker) + + invoker.EXPECT().GetUrl().Return(failsafeUrl) + + mockResult := &protocol.RPCResult{Err: perrors.New("error")} + + invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) + result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + + assert.NoError(t, result.Error()) + assert.Nil(t, result.Result()) +} diff --git a/go.mod b/go.mod index 28a3fc7c4202212a7664193b710044cefb406955..fb7e313c718ec5d755c4d2160b3d07f2f3d8474f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/apache/dubbo-go require ( + github.com/Workiva/go-datastructures v1.0.50 github.com/apache/dubbo-go-hessian2 v1.2.5-0.20190731020727-1697039810c8 github.com/davecgh/go-spew v1.1.1 // indirect github.com/dubbogo/getty v1.2.0 diff --git a/go.sum b/go.sum index e2947876e1ec1d8b45c160bb9fff356798898e21..bce2b9a98a1be18eed0d7e8a81cb8cea6f950c1d 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo= +github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/apache/dubbo-go-hessian2 v1.2.5-0.20190731020727-1697039810c8 h1:7zJlM+8bpCAUhv03TZnXkT4MLlLWng1s7An8CLuN73E= github.com/apache/dubbo-go-hessian2 v1.2.5-0.20190731020727-1697039810c8/go.mod h1:LWnndnrFXZmJLAzoyNAPNHSIJ1KOHVkTSsHgC3YYWlo= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -41,6 +43,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=