retry.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates. All rights reserved.
  2. // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
  3. package common
  4. import (
  5. "context"
  6. "fmt"
  7. "math/rand"
  8. "runtime"
  9. "time"
  10. )
  11. const (
  12. // UnlimitedNumAttemptsValue is the value for indicating unlimited attempts for reaching success
  13. UnlimitedNumAttemptsValue = uint(0)
  14. // number of characters contained in the generated retry token
  15. generatedRetryTokenLength = 32
  16. )
  17. // OCIRetryableRequest represents a request that can be reissued according to the specified policy.
  18. type OCIRetryableRequest interface {
  19. // Any retryable request must implement the OCIRequest interface
  20. OCIRequest
  21. // Each operation specifies default retry behavior. By passing no arguments to this method, the default retry
  22. // behavior, as determined on a per-operation-basis, will be honored. Variadic retry policy option arguments
  23. // passed to this method will override the default behavior.
  24. RetryPolicy() *RetryPolicy
  25. }
  26. // OCIOperationResponse represents the output of an OCIOperation, with additional context of error message
  27. // and operation attempt number.
  28. type OCIOperationResponse struct {
  29. // Response from OCI Operation
  30. Response OCIResponse
  31. // Error from OCI Operation
  32. Error error
  33. // Operation Attempt Number (one-based)
  34. AttemptNumber uint
  35. }
  36. // NewOCIOperationResponse assembles an OCI Operation Response object.
  37. func NewOCIOperationResponse(response OCIResponse, err error, attempt uint) OCIOperationResponse {
  38. return OCIOperationResponse{
  39. Response: response,
  40. Error: err,
  41. AttemptNumber: attempt,
  42. }
  43. }
  44. // RetryPolicy is the class that holds all relevant information for retrying operations.
  45. type RetryPolicy struct {
  46. // MaximumNumberAttempts is the maximum number of times to retry a request. Zero indicates an unlimited
  47. // number of attempts.
  48. MaximumNumberAttempts uint
  49. // ShouldRetryOperation inspects the http response, error, and operation attempt number, and
  50. // - returns true if we should retry the operation
  51. // - returns false otherwise
  52. ShouldRetryOperation func(OCIOperationResponse) bool
  53. // GetNextDuration computes the duration to pause between operation retries.
  54. NextDuration func(OCIOperationResponse) time.Duration
  55. }
  56. // NoRetryPolicy is a helper method that assembles and returns a return policy that indicates an operation should
  57. // never be retried (the operation is performed exactly once).
  58. func NoRetryPolicy() RetryPolicy {
  59. dontRetryOperation := func(OCIOperationResponse) bool { return false }
  60. zeroNextDuration := func(OCIOperationResponse) time.Duration { return 0 * time.Second }
  61. return NewRetryPolicy(uint(1), dontRetryOperation, zeroNextDuration)
  62. }
  63. // NewRetryPolicy is a helper method for assembling a Retry Policy object.
  64. func NewRetryPolicy(attempts uint, retryOperation func(OCIOperationResponse) bool, nextDuration func(OCIOperationResponse) time.Duration) RetryPolicy {
  65. return RetryPolicy{
  66. MaximumNumberAttempts: attempts,
  67. ShouldRetryOperation: retryOperation,
  68. NextDuration: nextDuration,
  69. }
  70. }
  71. // shouldContinueIssuingRequests returns true if we should continue retrying a request, based on the current attempt
  72. // number and the maximum number of attempts specified, or false otherwise.
  73. func shouldContinueIssuingRequests(current, maximum uint) bool {
  74. return maximum == UnlimitedNumAttemptsValue || current <= maximum
  75. }
  76. // RetryToken generates a retry token that must be included on any request passed to the Retry method.
  77. func RetryToken() string {
  78. alphanumericChars := []rune("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
  79. retryToken := make([]rune, generatedRetryTokenLength)
  80. for i := range retryToken {
  81. retryToken[i] = alphanumericChars[rand.Intn(len(alphanumericChars))]
  82. }
  83. return string(retryToken)
  84. }
  85. // Retry is a package-level operation that executes the retryable request using the specified operation and retry policy.
  86. func Retry(ctx context.Context, request OCIRetryableRequest, operation OCIOperation, policy RetryPolicy) (OCIResponse, error) {
  87. type retrierResult struct {
  88. response OCIResponse
  89. err error
  90. }
  91. var response OCIResponse
  92. var err error
  93. retrierChannel := make(chan retrierResult)
  94. go func() {
  95. // Deal with panics more graciously
  96. defer func() {
  97. if r := recover(); r != nil {
  98. stackBuffer := make([]byte, 1024)
  99. bytesWritten := runtime.Stack(stackBuffer, false)
  100. stack := string(stackBuffer[:bytesWritten])
  101. retrierChannel <- retrierResult{nil, fmt.Errorf("panicked while retrying operation. Panic was: %s\nStack: %s", r, stack)}
  102. }
  103. }()
  104. // use a one-based counter because it's easier to think about operation retry in terms of attempt numbering
  105. for currentOperationAttempt := uint(1); shouldContinueIssuingRequests(currentOperationAttempt, policy.MaximumNumberAttempts); currentOperationAttempt++ {
  106. Debugln(fmt.Sprintf("operation attempt #%v", currentOperationAttempt))
  107. response, err = operation(ctx, request)
  108. operationResponse := NewOCIOperationResponse(response, err, currentOperationAttempt)
  109. if !policy.ShouldRetryOperation(operationResponse) {
  110. // we should NOT retry operation based on response and/or error => return
  111. retrierChannel <- retrierResult{response, err}
  112. return
  113. }
  114. duration := policy.NextDuration(operationResponse)
  115. //The following condition is kept for backwards compatibility reasons
  116. if deadline, ok := ctx.Deadline(); ok && time.Now().Add(duration).After(deadline) {
  117. // we want to retry the operation, but the policy is telling us to wait for a duration that exceeds
  118. // the specified overall deadline for the operation => instead of waiting for however long that
  119. // time period is and then aborting, abort now and save the cycles
  120. retrierChannel <- retrierResult{response, DeadlineExceededByBackoff}
  121. return
  122. }
  123. Debugln(fmt.Sprintf("waiting %v before retrying operation", duration))
  124. // sleep before retrying the operation
  125. <-time.After(duration)
  126. }
  127. retrierChannel <- retrierResult{response, err}
  128. }()
  129. select {
  130. case <-ctx.Done():
  131. return response, ctx.Err()
  132. case result := <-retrierChannel:
  133. return result.response, result.err
  134. }
  135. }