package failsafe
import (
"context"
"sync"
"sync/atomic"
"time"
"github.com/failsafe-go/failsafe-go/common"
)
type ExecutionInfo interface {
Context () context .Context
Attempts () int
Executions () int
Retries () int
Hedges () int
StartTime () time .Time
ElapsedTime () time .Duration
}
type ExecutionAttempt [R any ] interface {
ExecutionInfo
LastResult () R
LastError () error
IsFirstAttempt () bool
IsRetry () bool
IsHedge () bool
AttemptStartTime () time .Time
ElapsedAttemptTime () time .Duration
}
type Execution [R any ] interface {
ExecutionAttempt [R ]
IsCanceled () bool
Canceled () <-chan struct {}
}
var closedChan chan any
func init() {
closedChan = make (chan any , 1 )
close (closedChan )
}
type execution[R any ] struct {
mtx *sync .Mutex
startTime time .Time
attempts *atomic .Uint32
retries *atomic .Uint32
hedges *atomic .Uint32
executions *atomic .Uint32
ctx context .Context
cancelFunc context .CancelFunc
canceledResult **common .PolicyResult [R ]
attemptStartTime time .Time
isHedge bool
lastResult R
lastError error
}
var _ Execution [any ] = &execution [any ]{}
var _ ExecutionInfo = &execution [any ]{}
func (e *execution [R ]) Attempts () int {
return int (e .attempts .Load ())
}
func (e *execution [R ]) Executions () int {
return int (e .executions .Load ())
}
func (e *execution [R ]) Retries () int {
return int (e .retries .Load ())
}
func (e *execution [R ]) Hedges () int {
return int (e .hedges .Load ())
}
func (e *execution [R ]) StartTime () time .Time {
return e .startTime
}
func (e *execution [R ]) IsFirstAttempt () bool {
return e .attempts .Load () == 1
}
func (e *execution [R ]) IsRetry () bool {
return e .attempts .Load () > 1
}
func (e *execution [R ]) IsHedge () bool {
return e .isHedge
}
func (e *execution [R ]) ElapsedTime () time .Duration {
return time .Since (e .startTime )
}
func (e *execution [R ]) LastResult () R {
return e .lastResult
}
func (e *execution [R ]) LastError () error {
if e .lastError == nil && e .ctx .Err () != nil {
return e .ctx .Err ()
}
return e .lastError
}
func (e *execution [R ]) Context () context .Context {
return e .ctx
}
func (e *execution [R ]) AttemptStartTime () time .Time {
return e .attemptStartTime
}
func (e *execution [_ ]) ElapsedAttemptTime () time .Duration {
return time .Since (e .attemptStartTime )
}
func (e *execution [_ ]) IsCanceled () bool {
return e .ctx .Err () != nil
}
func (e *execution [_ ]) Canceled () <-chan struct {} {
return e .ctx .Done ()
}
func (e *execution [R ]) RecordResult (result *common .PolicyResult [R ]) *common .PolicyResult [R ] {
e .mtx .Lock ()
defer e .mtx .Unlock ()
if canceled , cancelResult := e .isCanceledWithResult (); canceled {
return cancelResult
}
if result != nil {
e .lastResult = result .Result
e .lastError = result .Error
}
return nil
}
func (e *execution [R ]) InitializeRetry () *common .PolicyResult [R ] {
e .mtx .Lock ()
defer e .mtx .Unlock ()
if canceled , cancelResult := e .isCanceledWithResult (); canceled {
return cancelResult
}
if e .attempts .Add (1 ) > 1 {
e .retries .Add (1 )
}
e .attemptStartTime = time .Now ()
*e .canceledResult = nil
return nil
}
func (e *execution [R ]) Cancel (result *common .PolicyResult [R ]) {
e .mtx .Lock ()
defer e .mtx .Unlock ()
if canceled , _ := e .isCanceledWithResult (); canceled {
return
}
*e .canceledResult = result
if result != nil {
e .lastResult = result .Result
e .lastError = result .Error
}
if e .cancelFunc != nil {
e .cancelFunc ()
}
}
func (e *execution [R ]) IsCanceledWithResult () (bool , *common .PolicyResult [R ]) {
e .mtx .Lock ()
defer e .mtx .Unlock ()
return e .isCanceledWithResult ()
}
func (e *execution [R ]) isCanceledWithResult () (bool , *common .PolicyResult [R ]) {
if e .ctx .Err () != nil {
if *e .canceledResult == nil {
return true , &common .PolicyResult [R ]{
Error : e .ctx .Err (),
Done : true ,
}
}
return true , *e .canceledResult
}
return false , nil
}
func (e *execution [R ]) CopyWithResult (result *common .PolicyResult [R ]) Execution [R ] {
c := e .copy ()
if result != nil {
c .lastResult = result .Result
c .lastError = result .Error
}
return c
}
func (e *execution [R ]) CopyForCancellable () Execution [R ] {
c := e .copy ()
c .ctx , c .cancelFunc = context .WithCancel (c .ctx )
return c
}
func (e *execution [R ]) CopyForHedge () Execution [R ] {
c := e .copy ()
c .isHedge = true
c .attempts .Add (1 )
c .hedges .Add (1 )
c .ctx , c .cancelFunc = context .WithCancel (c .ctx )
return c
}
func (e *execution [R ]) copy () *execution [R ] {
e .mtx .Lock ()
c := *e
e .mtx .Unlock ()
return &c
}
func (e *execution [R ]) record () {
e .executions .Add (1 )
}
func newExecution[R any ](ctx context .Context ) *execution [R ] {
attempts := atomic .Uint32 {}
retries := atomic .Uint32 {}
hedges := atomic .Uint32 {}
executions := atomic .Uint32 {}
attempts .Add (1 )
var canceledResult *common .PolicyResult [R ]
now := time .Now ()
return &execution [R ]{
ctx : ctx ,
mtx : &sync .Mutex {},
attempts : &attempts ,
retries : &retries ,
hedges : &hedges ,
executions : &executions ,
canceledResult : &canceledResult ,
attemptStartTime : now ,
startTime : now ,
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .