From b310469bb66b0ae82731862874627d6a466f13b4 Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Sat, 2 Nov 2024 16:51:59 +0800 Subject: [PATCH] =?UTF-8?q?retry:=20=E5=A2=9E=E5=8A=A0=E9=97=AD=E5=8C=85?= =?UTF-8?q?=20Retry=20=E6=96=B9=E6=B3=95=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=87=8D=E5=A4=8D=E5=86=99=E7=B1=BB=E4=BC=BC?= =?UTF-8?q?=E7=9A=84=E5=9E=83=E5=9C=BE=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/errs/error.go | 4 +++ retry/retry.go | 55 +++++++++++++++++++++++++++++++++++ retry/retry_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 retry/retry.go create mode 100644 retry/retry_test.go diff --git a/internal/errs/error.go b/internal/errs/error.go index d05f0494..5669f127 100644 --- a/internal/errs/error.go +++ b/internal/errs/error.go @@ -36,3 +36,7 @@ func NewErrInvalidIntervalValue(interval time.Duration) error { func NewErrInvalidMaxIntervalValue(maxInterval, initialInterval time.Duration) error { return fmt.Errorf("ekit: 最大重试间隔的时间 [%d] 应大于等于初始重试的间隔时间 [%d] ", maxInterval, initialInterval) } + +func NewErrRetryExhausted(lastErr error) error { + return fmt.Errorf("ekit: 超过最大重试次数,业务返回的最后一个 error %w", lastErr) +} diff --git a/retry/retry.go b/retry/retry.go new file mode 100644 index 00000000..f1d65b53 --- /dev/null +++ b/retry/retry.go @@ -0,0 +1,55 @@ +// Copyright 2021 ecodeclub +// +// 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 retry + +import ( + "context" + "time" + + "github.com/ecodeclub/ekit/internal/errs" +) + +func Retry(ctx context.Context, + s Strategy, + bizFunc func() error) error { + var ticker *time.Ticker + defer func() { + if ticker != nil { + ticker.Stop() + } + }() + for { + err := bizFunc() + // 直接退出 + if err == nil { + return nil + } + duration, ok := s.Next() + if !ok { + return errs.NewErrRetryExhausted(err) + } + if ticker == nil { + ticker = time.NewTicker(duration) + } else { + ticker.Reset(duration) + } + select { + case <-ctx.Done(): + // 超时或者被取消了,直接返回 + return ctx.Err() + case <-ticker.C: + } + } +} diff --git a/retry/retry_test.go b/retry/retry_test.go new file mode 100644 index 00000000..9f3aa883 --- /dev/null +++ b/retry/retry_test.go @@ -0,0 +1,66 @@ +// Copyright 2021 ecodeclub +// +// 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 retry + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRetry(t *testing.T) { + bizErr := errors.New("biz error") + testCases := []struct { + name string + biz func() error + strategy Strategy + wantError error + }{ + { + name: "第一次就成功", + biz: func() error { + t.Log("模拟业务") + return nil + }, + strategy: func() Strategy { + res, _ := NewFixedIntervalRetryStrategy(time.Second, 3) + return res + }(), + }, + { + name: "重试最终失败", + biz: func() error { + return bizErr + }, + strategy: func() Strategy { + res, _ := NewFixedIntervalRetryStrategy(time.Second, 3) + return res + }(), + wantError: bizErr, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + err := Retry(ctx, tc.strategy, tc.biz) + assert.ErrorIs(t, err, tc.wantError) + }) + } +}