diff --git a/internal/util/new_retry/options.go b/internal/util/new_retry/options.go new file mode 100644 index 0000000000..ba312396a5 --- /dev/null +++ b/internal/util/new_retry/options.go @@ -0,0 +1,48 @@ +// Copyright (C) 2019-2020 Zilliz. All rights reserved. +// +// 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 "time" + +type Config struct { + attempts uint + sleep time.Duration + maxSleepTime time.Duration +} + +func NewDefaultConfig() *Config { + return &Config{ + attempts: uint(10), + sleep: 200 * time.Millisecond, + maxSleepTime: 1 * time.Second, + } +} + +type Option func(*Config) + +func Attempts(attempts uint) Option { + return func(c *Config) { + c.attempts = attempts + } +} + +func Sleep(sleep time.Duration) Option { + return func(c *Config) { + c.sleep = sleep + } +} + +func MaxSleepTime(maxSleepTime time.Duration) Option { + return func(c *Config) { + c.maxSleepTime = maxSleepTime + } +} diff --git a/internal/util/new_retry/retry.go b/internal/util/new_retry/retry.go new file mode 100644 index 0000000000..02ff18ffa2 --- /dev/null +++ b/internal/util/new_retry/retry.go @@ -0,0 +1,71 @@ +// Copyright (C) 2019-2020 Zilliz. All rights reserved. +// +// 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" + "fmt" + "strings" + "time" +) + +func Do(ctx context.Context, fn func() error, opts ...Option) error { + + c := NewDefaultConfig() + + for _, opt := range opts { + opt(c) + } + el := make(ErrorList, c.attempts) + + for i := uint(0); i < c.attempts; i++ { + if err := fn(); err != nil { + if s, ok := err.(InterruptError); ok { + return s.error + } + el[i] = err + + select { + case <-time.After(c.sleep): + case <-ctx.Done(): + return ctx.Err() + } + + c.sleep *= 2 + if c.sleep > c.maxSleepTime { + c.sleep = c.maxSleepTime + } + } else { + return nil + } + } + return el +} + +type ErrorList []error + +func (el ErrorList) Error() string { + var builder strings.Builder + builder.WriteString("All attempts results:\n") + for index, err := range el { + builder.WriteString(fmt.Sprintf("attempt #%d:%s\n", index+1, err.Error())) + } + return builder.String() +} + +type InterruptError struct { + error +} + +func NoRetryError(err error) InterruptError { + return InterruptError{err} +} diff --git a/internal/util/new_retry/retry_test.go b/internal/util/new_retry/retry_test.go new file mode 100644 index 0000000000..860dcb2800 --- /dev/null +++ b/internal/util/new_retry/retry_test.go @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2020 Zilliz. All rights reserved. +// +// 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" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestDo(t *testing.T) { + ctx := context.Background() + + n := 0 + testFn := func() error { + if n < 3 { + n++ + return fmt.Errorf("some error") + } + return nil + } + + err := Do(ctx, testFn) + assert.Nil(t, err) +} + +func TestAttempts(t *testing.T) { + ctx := context.Background() + + testFn := func() error { + return fmt.Errorf("some error") + } + + err := Do(ctx, testFn, Attempts(1)) + assert.NotNil(t, err) + fmt.Println(err) +} + +func TestMaxSleepTime(t *testing.T) { + ctx := context.Background() + + testFn := func() error { + return fmt.Errorf("some error") + } + + err := Do(ctx, testFn, Attempts(3), MaxSleepTime(200*time.Millisecond)) + assert.NotNil(t, err) + fmt.Println(err) +} + +func TestSleep(t *testing.T) { + ctx := context.Background() + + testFn := func() error { + return fmt.Errorf("some error") + } + + err := Do(ctx, testFn, Attempts(3), Sleep(500*time.Millisecond)) + assert.NotNil(t, err) + fmt.Println(err) +} + +func TestAllError(t *testing.T) { + ctx := context.Background() + + testFn := func() error { + return fmt.Errorf("some error") + } + + err := Do(ctx, testFn, Attempts(3)) + assert.NotNil(t, err) + fmt.Println(err) +} + +func TestContextDeadline(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + testFn := func() error { + return fmt.Errorf("some error") + } + + err := Do(ctx, testFn) + assert.NotNil(t, err) + fmt.Println(err) +} + +func TestContextCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + testFn := func() error { + return fmt.Errorf("some error") + } + + go func() { + time.Sleep(1 * time.Second) + cancel() + }() + + err := Do(ctx, testFn) + assert.NotNil(t, err) + fmt.Println(err) +} diff --git a/internal/util/retry/options.go b/internal/util/retry/options.go new file mode 100644 index 0000000000..ba312396a5 --- /dev/null +++ b/internal/util/retry/options.go @@ -0,0 +1,48 @@ +// Copyright (C) 2019-2020 Zilliz. All rights reserved. +// +// 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 "time" + +type Config struct { + attempts uint + sleep time.Duration + maxSleepTime time.Duration +} + +func NewDefaultConfig() *Config { + return &Config{ + attempts: uint(10), + sleep: 200 * time.Millisecond, + maxSleepTime: 1 * time.Second, + } +} + +type Option func(*Config) + +func Attempts(attempts uint) Option { + return func(c *Config) { + c.attempts = attempts + } +} + +func Sleep(sleep time.Duration) Option { + return func(c *Config) { + c.sleep = sleep + } +} + +func MaxSleepTime(maxSleepTime time.Duration) Option { + return func(c *Config) { + c.maxSleepTime = maxSleepTime + } +}