Created
June 14, 2024 02:36
-
-
Save dixonwhitmire/a656e65b5e4f0e9f6dc1f3f8f78abcef to your computer and use it in GitHub Desktop.
retry io functions in golang
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
// Package retry supports retrying operations supported within the [RetryOperation] type set. | |
package retry | |
import ( | |
"fmt" | |
"log/slog" | |
"os" | |
"time" | |
) | |
const ( | |
// MaxBackOffSeconds sets the upper limit on the number of seconds used to calculate the duration between retries. | |
MaxBackOffSeconds = 10 | |
// MaxRetryCount sets the upper limit on retries. | |
MaxRetryCount = 10 | |
) | |
// OpenerFunc supports opening a file returning a handle or an error. | |
type OpenerFunc func(filePath string) (*os.File, error) | |
// CsvReaderFunc supports reading a csv record from file or an error. | |
type CsvReaderFunc func() ([]string, error) | |
// CsvWriterFunc supports writing a csv record to file or returning an error. | |
type CsvWriterFunc func([]string) error | |
// RetryableConstraint is used to constrain the types of supported retryable operations. | |
type RetryableConstraint interface { | |
OpenerFunc | CsvReaderFunc | CsvWriterFunc | |
} | |
// Try executes operation `t` with its argument, `operationArg` enabling retries up to `retryCount` times. | |
// The duration between retry attempts is calculated using the `currentCount` * `retryBackOffSeconds`. | |
// `retryCount` defaults to 1 if it set to 0 and is capped at 10 times. | |
// `retryBackOffSeconds` is capped to 10 seconds | |
func Try[T RetryableConstraint](t T, operationArg any, retryCount uint, retryBackOffSeconds uint) (any, error) { | |
// set our reasonable defaults/limits | |
if retryCount == 0 { | |
retryCount = 1 | |
} | |
if retryCount > 10 { | |
retryCount = MaxRetryCount | |
} | |
if retryBackOffSeconds > 10 { | |
retryBackOffSeconds = MaxBackOffSeconds | |
} | |
var err error | |
var funcReturn any | |
var warningLogMessage string | |
// use 1 based indexed to align with the provided retry count | |
for currentCount := uint(1); currentCount <= retryCount; currentCount++ { | |
switch retryFunc := any(t).(type) { | |
case OpenerFunc: | |
filePath := operationArg.(string) | |
funcReturn, err = retryFunc(filePath) | |
warningLogMessage = fmt.Sprintf("error opening file %s", filePath) | |
case CsvReaderFunc: | |
funcReturn, err = retryFunc() | |
warningLogMessage = "error reading from csv file" | |
case CsvWriterFunc: | |
csvRecord := operationArg.([]string) | |
err = retryFunc(csvRecord) | |
warningLogMessage = "error writing to csv file" | |
} | |
if err != nil { | |
logWarningAndWait(warningLogMessage, currentCount, retryCount, retryBackOffSeconds) | |
continue | |
} | |
break | |
} | |
return funcReturn, err | |
} | |
// logWarningAndWait emits a warning log message using the configured logger with details regarding the error, and then | |
// waits. | |
func logWarningAndWait(logMessage string, currentCount uint, retryCount uint, retryBackOffSeconds uint) { | |
slog.Warn(logMessage, | |
"current_attempt", currentCount, | |
"retries_remaining", retryCount-currentCount) | |
waitDuration := time.Duration(currentCount*retryBackOffSeconds) * time.Second | |
time.Sleep(waitDuration) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment