-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: add initial retry mechanism (#2)
* chore: add initial retry mechanism * refactor: use defaultValkyrietry in defaultValkyrietryWithContext * refactor: use time.timer directly instead of abstract it * refactor: simplify the retry mechanism runner function into two
- Loading branch information
1 parent
9ae8da1
commit 99f584a
Showing
11 changed files
with
573 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package valkyrietry | ||
|
||
import "time" | ||
|
||
const ( | ||
DefaultMaxRetryAttempt = 5 | ||
DefaultRetryDelay = time.Duration(0.5 * float64(time.Second)) | ||
DefaultRetryBackoffMultiplier = 1.5 | ||
DefaultJitter = 0.5 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package valkyrietry | ||
|
||
import "fmt" | ||
|
||
var ( | ||
ErrMaxRetryAttemptsExceeded = fmt.Errorf("function is failed to retry after max attemps retries") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/ruang-guru/valkyrietry" | ||
) | ||
|
||
func main() { | ||
ctx := context.Background() | ||
|
||
options := []valkyrietry.Option{ | ||
valkyrietry.WithMaxRetryAttempts(1), | ||
valkyrietry.WithRetryDelay(2 * time.Second), | ||
valkyrietry.WithRetryBackoffMultiplier(2), | ||
valkyrietry.WithJitter(0.2), | ||
} | ||
|
||
retryFunc := func() error { | ||
resp, err := http.Get("http://testingexample.com") | ||
if err != nil { | ||
fmt.Println("Request failed, will retry:", err) | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode >= 500 { | ||
// Simulate server-side error | ||
return errors.New("server error, retrying") | ||
} | ||
fmt.Println("Request succeeded") | ||
return nil | ||
} | ||
|
||
// Use Valkyrietry to handle the retry logic | ||
if err := valkyrietry.Do(ctx, retryFunc, options...); err != nil { | ||
fmt.Println("Operation failed after retries:", err) | ||
return | ||
} | ||
|
||
fmt.Println("Operation successful") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/ruang-guru/valkyrietry | ||
|
||
go 1.20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package valkyrietry | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
type Configuration struct { | ||
MaxRetryAttempts uint | ||
InitialRetryDelay time.Duration | ||
RetryBackoffMultiplier float32 | ||
JitterPercentage float32 | ||
} | ||
|
||
// option is a function option used to configure a Valkyrietry. | ||
type Option func(c *Configuration) | ||
|
||
// WithMaxRetryAttempts | ||
// Set the maximum number of retry attempts for the retry mechanism. | ||
// | ||
// if you set the attempt to 0, it means it will run until the process succed | ||
func WithMaxRetryAttempts(attempt uint) Option { | ||
return func(c *Configuration) { | ||
c.MaxRetryAttempts = attempt | ||
} | ||
} | ||
|
||
// WithRetryDelay | ||
// Set the initial duration value for the first retry. | ||
func WithRetryDelay(delay time.Duration) Option { | ||
return func(c *Configuration) { | ||
c.InitialRetryDelay = delay | ||
} | ||
} | ||
|
||
// WithRetryBackoffMultiplier | ||
// Set the multiplier for each failed retry attempt. | ||
// Formula: initial retry delay * multiplier. | ||
func WithRetryBackoffMultiplier(multiplier float32) Option { | ||
return func(c *Configuration) { | ||
c.RetryBackoffMultiplier = multiplier | ||
} | ||
} | ||
|
||
// WithJitter | ||
// Set the percentage of jitter value to determine the lowest and highest | ||
// random values. | ||
func WithJitter(percentage float32) Option { | ||
return func(c *Configuration) { | ||
c.JitterPercentage = percentage | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package valkyrietry | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestWithMaxRetryAttempts(t *testing.T) { | ||
expectedAttempts := uint(5) | ||
config := &Configuration{} | ||
option := WithMaxRetryAttempts(expectedAttempts) | ||
option(config) | ||
|
||
if config.MaxRetryAttempts != expectedAttempts { | ||
t.Errorf("WithMaxRetryAttempts() = %v, want %v", config.MaxRetryAttempts, expectedAttempts) | ||
} | ||
} | ||
|
||
func TestWithRetryDelay(t *testing.T) { | ||
expectedDelay := 100 * time.Millisecond | ||
config := &Configuration{} | ||
option := WithRetryDelay(expectedDelay) | ||
option(config) | ||
|
||
if config.InitialRetryDelay != expectedDelay { | ||
t.Errorf("WithRetryDelay() = %v, want %v", config.InitialRetryDelay, expectedDelay) | ||
} | ||
} | ||
|
||
func TestWithRetryBackoffMultiplier(t *testing.T) { | ||
expectedMultiplier := float32(2.0) | ||
config := &Configuration{} | ||
option := WithRetryBackoffMultiplier(expectedMultiplier) | ||
option(config) | ||
|
||
if config.RetryBackoffMultiplier != expectedMultiplier { | ||
t.Errorf("WithRetryBackoffMultiplier() = %v, want %v", config.RetryBackoffMultiplier, expectedMultiplier) | ||
} | ||
} | ||
|
||
func TestWithJitter(t *testing.T) { | ||
expectedJitter := float32(0.25) | ||
config := &Configuration{} | ||
option := WithJitter(expectedJitter) | ||
option(config) | ||
|
||
if config.JitterPercentage != expectedJitter { | ||
t.Errorf("WithJitter() = %v, want %v", config.JitterPercentage, expectedJitter) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package valkyrietry | ||
|
||
import "time" | ||
|
||
type Timer struct { | ||
timer *time.Timer | ||
} | ||
|
||
func NewTimer() *Timer { | ||
return &Timer{} | ||
} | ||
|
||
// Start | ||
// Set the timer for the specified duration. | ||
// If the current timer is nil, initialize a new one; | ||
// otherwise, reset it to the new duration. | ||
func (t *Timer) Start(duration time.Duration) { | ||
if t.timer == nil { | ||
t.timer = time.NewTimer(duration) | ||
return | ||
} | ||
|
||
t.timer.Reset(duration) | ||
} | ||
|
||
// Stop | ||
// Stop the current timer. | ||
func (t *Timer) Stop() { | ||
if t.timer != nil { | ||
t.timer.Stop() | ||
} | ||
} | ||
|
||
// C | ||
// Retrieve the channel when either the timer stops or the timer completes. | ||
func (t *Timer) C() <-chan time.Time { | ||
return t.timer.C | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package valkyrietry | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestTimerStart(t *testing.T) { | ||
timer := NewTimer() | ||
duration := 100 * time.Millisecond | ||
|
||
start := time.Now() | ||
timer.Start(duration) | ||
<-timer.C() | ||
|
||
if time.Since(start) < duration { | ||
t.Errorf("Timer fired before the expected duration") | ||
} | ||
} | ||
|
||
func TestTimerReset(t *testing.T) { | ||
timer := NewTimer() | ||
firstDuration := 50 * time.Millisecond | ||
secondDuration := 100 * time.Millisecond | ||
|
||
timer.Start(firstDuration) | ||
time.Sleep(30 * time.Millisecond) | ||
timer.Start(secondDuration) | ||
|
||
start := time.Now() | ||
<-timer.C() | ||
|
||
if elapsed := time.Since(start); elapsed < secondDuration { | ||
t.Errorf("Timer fired before the expected reset duration, elapsed: %v", elapsed) | ||
} | ||
} | ||
|
||
func TestTimerStop(t *testing.T) { | ||
timer := NewTimer() | ||
duration := 100 * time.Millisecond | ||
|
||
timer.Start(duration) | ||
timer.Stop() | ||
|
||
select { | ||
case <-timer.C(): | ||
t.Errorf("Timer channel should not receive after being stopped") | ||
case <-time.After(150 * time.Millisecond): | ||
} | ||
} |
Oops, something went wrong.