-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from kinfinity/feat/timeout
Timeout Pattern
- Loading branch information
Showing
7 changed files
with
191 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
name: BUILD & TESTS | ||
name: Build & Tests | ||
on: | ||
workflow_dispatch: | ||
pull_request: | ||
|
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
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,37 @@ | ||
# Timeout Pattern in Go | ||
|
||
The Timeout pattern is used to limit the execution time of a function or operation. It ensures that the operation completes within a specified time duration, and if it exceeds that duration, it either returns a default value or invokes a fallback function. | ||
|
||
## Usage | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/kinfinity/distributed-resilience/timeout" | ||
) | ||
|
||
func main() { | ||
// Create a Timeout instance with a fallback function | ||
timeout := timeout.NewTimeOutWithFallback(5 * time.Second, func() error { | ||
// Custom fallback logic here | ||
return nil | ||
}) | ||
|
||
// Watch for timeout | ||
result := timeout.Watch(executionCompletionChan) | ||
if result.result { | ||
// Operation completed within the timeout duration | ||
} else { | ||
// Operation timed out | ||
} | ||
} | ||
``` | ||
|
||
# **References** | ||
|
||
- [ All you need to know about timeouts - Zalando ](https://engineering.zalando.com/posts/2023/07/all-you-need-to-know-about-timeouts.html) |
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,73 @@ | ||
package timeout | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
) | ||
|
||
// Timeout | ||
type TimeOut struct { | ||
duration time.Duration | ||
fallback func() error | ||
} | ||
|
||
// Returns nil if the operation times out | ||
type TimeOutResult struct { | ||
result bool | ||
} | ||
|
||
// TimeOut | ||
func NewTimeOut(Delay time.Duration) *TimeOut { | ||
return &TimeOut{ | ||
duration: Delay, | ||
} | ||
} | ||
|
||
// TimeOut with options via | ||
// fallback() error | ||
func NewTimeOutWithFallback(Delay time.Duration, options ...interface{}) *TimeOut { | ||
|
||
timeout := &TimeOut{ | ||
duration: Delay, | ||
} | ||
|
||
for _, option := range options { | ||
switch opt := option.(type) { | ||
case func() error: | ||
timeout.fallback = opt | ||
default: | ||
panic(fmt.Sprintf("Unknown option type: %T", opt)) | ||
} | ||
} | ||
|
||
return timeout | ||
} | ||
|
||
// Creates a context with the duration lifetime | ||
// func executes till the end| Context Lifetime expires | ||
func (to *TimeOut) Watch(executionCompletionChan chan bool) *TimeOutResult { | ||
// Build Context Lifetime | ||
ctx, cancel := context.WithTimeout(context.Background(), to.duration) | ||
defer cancel() | ||
|
||
for { | ||
select { | ||
case result := <-executionCompletionChan: | ||
return &TimeOutResult{ | ||
result: result, | ||
} | ||
case <-ctx.Done(): | ||
if to.fallback != nil { | ||
to.fallback() // fallback on timeout | ||
} | ||
// Timeout | ||
return &TimeOutResult{} | ||
default: | ||
} | ||
// random delay before we check conditions again | ||
// give timeout grace | ||
time.Sleep(to.duration / 4) | ||
} | ||
|
||
} |
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,74 @@ | ||
package timeout | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/mock" | ||
) | ||
|
||
// Mock function | ||
type MockFunction struct { | ||
mock.Mock | ||
} | ||
|
||
func (m *MockFunction) Execute() error { | ||
args := m.Called() | ||
return args.Error(0) | ||
} | ||
|
||
func simulateTimeOut(completionChan chan<- bool) { | ||
time.Sleep(10 * time.Second) | ||
completionChan <- true | ||
} | ||
|
||
// TestTimeOutWait | ||
func TestTimeOutWait(t *testing.T) { | ||
// | ||
compChan := make(chan bool) | ||
go func() { simulateTimeOut(compChan) }() | ||
|
||
t.Run("TimeOut", func(t *testing.T) { | ||
time_delay := time.Duration(8 * time.Second) | ||
mockFn := new(MockFunction) | ||
mockFn.On("Fallback").Return(nil) | ||
|
||
timeout := NewTimeOut(time_delay) | ||
response := timeout.Watch(compChan) | ||
|
||
assert.Empty(t, response.result) | ||
}) | ||
|
||
t.Run("Completion", func(t *testing.T) { | ||
time_delay := time.Duration(12 * time.Second) | ||
mockFn := new(MockFunction) | ||
mockFn.On("Fallback").Return(nil) | ||
|
||
timeout := NewTimeOut(time_delay) | ||
response := timeout.Watch(compChan) | ||
|
||
assert.NotEmpty(t, response.result) | ||
}) | ||
|
||
} | ||
|
||
// TestTimeOutWait | ||
func TestTimeOutWaitFallback(t *testing.T) { | ||
// | ||
time_delay := time.Duration(4 * time.Second) | ||
compChan := make(chan bool) | ||
go func() { simulateTimeOut(compChan) }() | ||
|
||
t.Run("FallbackExecutes", func(t *testing.T) { | ||
mockFn := new(MockFunction) | ||
mockFn.On("Execute").Return(nil).Times(int(1)) | ||
|
||
timeout := NewTimeOutWithFallback(time_delay, mockFn.Execute) | ||
response := timeout.Watch(compChan) | ||
|
||
assert.Empty(t, response.result) | ||
mockFn.AssertNumberOfCalls(t, "Execute", int(1)) | ||
}) | ||
|
||
} |