mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-19 23:42:05 +02:00
147 lines
6.0 KiB
Go
147 lines
6.0 KiB
Go
|
|
// Copyright (c) 2025 IBM Corp.
|
||
|
|
// All rights reserved.
|
||
|
|
//
|
||
|
|
// Licensed under the Apache LicensVersion 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 readerresult
|
||
|
|
|
||
|
|
import (
|
||
|
|
RS "github.com/IBM/fp-go/v2/context/readerresult"
|
||
|
|
F "github.com/IBM/fp-go/v2/function"
|
||
|
|
"github.com/IBM/fp-go/v2/result"
|
||
|
|
R "github.com/IBM/fp-go/v2/retry"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Retrying retries a ReaderResult computation according to a retry policy with context awareness.
|
||
|
|
//
|
||
|
|
// This is the idiomatic wrapper around the functional [github.com/IBM/fp-go/v2/context/readerresult.Retrying]
|
||
|
|
// function. It provides a more Go-friendly API by working with (value, error) tuples instead of Result types.
|
||
|
|
//
|
||
|
|
// The function implements a retry mechanism for operations that depend on a [context.Context] and can fail.
|
||
|
|
// It respects context cancellation, meaning that if the context is cancelled during retry delays, the
|
||
|
|
// operation will stop immediately and return the cancellation error.
|
||
|
|
//
|
||
|
|
// The retry loop will continue until one of the following occurs:
|
||
|
|
// - The action succeeds and the check function returns false (no retry needed)
|
||
|
|
// - The retry policy returns None (retry limit reached)
|
||
|
|
// - The check function returns false (indicating success or a non-retryable failure)
|
||
|
|
// - The context is cancelled (returns context.Canceled or context.DeadlineExceeded)
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
//
|
||
|
|
// - policy: A RetryPolicy that determines when and how long to wait between retries.
|
||
|
|
// The policy receives a RetryStatus on each iteration and returns an optional delay.
|
||
|
|
// If it returns None, retrying stops. Common policies include LimitRetries,
|
||
|
|
// ExponentialBackoff, and CapDelay from the retry package.
|
||
|
|
//
|
||
|
|
// - action: A Kleisli arrow that takes a RetryStatus and returns a ReaderResult[A].
|
||
|
|
// This function is called on each retry attempt and receives information about the
|
||
|
|
// current retry state (iteration number, cumulative delay, etc.). The action depends
|
||
|
|
// on a context.Context and produces (A, error). The context passed to the action
|
||
|
|
// will be the same context used for retry delays, so cancellation is properly propagated.
|
||
|
|
//
|
||
|
|
// - check: A predicate function that examines the result value and error, returning true
|
||
|
|
// if the operation should be retried, or false if it should stop. This allows you to
|
||
|
|
// distinguish between retryable failures (e.g., network timeouts) and permanent
|
||
|
|
// failures (e.g., invalid input). The function receives both the value and error from
|
||
|
|
// the action's result. Note that context cancellation errors will automatically stop
|
||
|
|
// retrying regardless of this function's return value.
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
//
|
||
|
|
// A ReaderResult[A] that, when executed with a context, will perform the retry
|
||
|
|
// logic with context cancellation support and return the final (value, error) tuple.
|
||
|
|
//
|
||
|
|
// Type Parameters:
|
||
|
|
// - A: The type of the success value
|
||
|
|
//
|
||
|
|
// Context Cancellation:
|
||
|
|
//
|
||
|
|
// The retry mechanism respects context cancellation in two ways:
|
||
|
|
// 1. During retry delays: If the context is cancelled while waiting between retries,
|
||
|
|
// the operation stops immediately and returns the context error.
|
||
|
|
// 2. During action execution: If the action itself checks the context and returns
|
||
|
|
// an error due to cancellation, the retry loop will stop (assuming the check
|
||
|
|
// function doesn't force a retry on context errors).
|
||
|
|
//
|
||
|
|
// Implementation Details:
|
||
|
|
//
|
||
|
|
// This function wraps the functional [github.com/IBM/fp-go/v2/context/readerresult.Retrying]
|
||
|
|
// by converting between the idiomatic (value, error) tuple representation and the functional
|
||
|
|
// Result[A] representation. The conversion is handled by ToReaderResult and FromReaderResult,
|
||
|
|
// ensuring seamless integration with the underlying retry mechanism that uses delayWithCancel
|
||
|
|
// to properly handle context cancellation during delays.
|
||
|
|
//
|
||
|
|
// Example:
|
||
|
|
//
|
||
|
|
// // Create a retry policy: exponential backoff with a cap, limited to 5 retries
|
||
|
|
// policy := retry.Monoid.Concat(
|
||
|
|
// retry.LimitRetries(5),
|
||
|
|
// retry.CapDelay(10*time.Second, retry.ExponentialBackoff(100*time.Millisecond)),
|
||
|
|
// )
|
||
|
|
//
|
||
|
|
// // Action that fetches data, with retry status information
|
||
|
|
// fetchData := func(status retry.RetryStatus) ReaderResult[string] {
|
||
|
|
// return func(ctx context.Context) (string, error) {
|
||
|
|
// // Check if context is cancelled
|
||
|
|
// if ctx.Err() != nil {
|
||
|
|
// return "", ctx.Err()
|
||
|
|
// }
|
||
|
|
// // Simulate an HTTP request that might fail
|
||
|
|
// if status.IterNumber < 3 {
|
||
|
|
// return "", fmt.Errorf("temporary error")
|
||
|
|
// }
|
||
|
|
// return "success", nil
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// // Check function: retry on any error except context cancellation
|
||
|
|
// shouldRetry := func(val string, err error) bool {
|
||
|
|
// return err != nil && !errors.Is(err, context.Canceled)
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// // Create the retrying computation
|
||
|
|
// retryingFetch := Retrying(policy, fetchData, shouldRetry)
|
||
|
|
//
|
||
|
|
// // Execute with a cancellable context
|
||
|
|
// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
|
|
// defer cancel()
|
||
|
|
// result, err := retryingFetch(ctx)
|
||
|
|
//
|
||
|
|
// See also:
|
||
|
|
// - retry.RetryPolicy for available retry policies
|
||
|
|
// - retry.RetryStatus for information passed to the action
|
||
|
|
// - context.Context for context cancellation semantics
|
||
|
|
// - github.com/IBM/fp-go/v2/context/readerresult.Retrying for the underlying functional implementation
|
||
|
|
//
|
||
|
|
//go:inline
|
||
|
|
func Retrying[A any](
|
||
|
|
policy R.RetryPolicy,
|
||
|
|
action Kleisli[R.RetryStatus, A],
|
||
|
|
check func(A, error) bool,
|
||
|
|
) ReaderResult[A] {
|
||
|
|
return F.Pipe1(
|
||
|
|
RS.Retrying(
|
||
|
|
policy,
|
||
|
|
F.Flow2(
|
||
|
|
action,
|
||
|
|
ToReaderResult,
|
||
|
|
),
|
||
|
|
func(a Result[A]) bool {
|
||
|
|
return check(result.Unwrap(a))
|
||
|
|
},
|
||
|
|
),
|
||
|
|
FromReaderResult,
|
||
|
|
)
|
||
|
|
}
|