call_option.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // Copyright 2016, Google Inc.
  2. // All rights reserved.
  3. //
  4. // Redistribution and use in source and binary forms, with or without
  5. // modification, are permitted provided that the following conditions are
  6. // met:
  7. //
  8. // * Redistributions of source code must retain the above copyright
  9. // notice, this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above
  11. // copyright notice, this list of conditions and the following disclaimer
  12. // in the documentation and/or other materials provided with the
  13. // distribution.
  14. // * Neither the name of Google Inc. nor the names of its
  15. // contributors may be used to endorse or promote products derived from
  16. // this software without specific prior written permission.
  17. //
  18. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. package gax
  30. import (
  31. "errors"
  32. "math/rand"
  33. "time"
  34. "google.golang.org/api/googleapi"
  35. "google.golang.org/grpc"
  36. "google.golang.org/grpc/codes"
  37. "google.golang.org/grpc/status"
  38. )
  39. // CallOption is an option used by Invoke to control behaviors of RPC calls.
  40. // CallOption works by modifying relevant fields of CallSettings.
  41. type CallOption interface {
  42. // Resolve applies the option by modifying cs.
  43. Resolve(cs *CallSettings)
  44. }
  45. // Retryer is used by Invoke to determine retry behavior.
  46. type Retryer interface {
  47. // Retry reports whether a request should be retried and how long to pause before retrying
  48. // if the previous attempt returned with err. Invoke never calls Retry with nil error.
  49. Retry(err error) (pause time.Duration, shouldRetry bool)
  50. }
  51. type retryerOption func() Retryer
  52. func (o retryerOption) Resolve(s *CallSettings) {
  53. s.Retry = o
  54. }
  55. // WithRetry sets CallSettings.Retry to fn.
  56. func WithRetry(fn func() Retryer) CallOption {
  57. return retryerOption(fn)
  58. }
  59. // OnErrorFunc returns a Retryer that retries if and only if the previous attempt
  60. // returns an error that satisfies shouldRetry.
  61. //
  62. // Pause times between retries are specified by bo. bo is only used for its
  63. // parameters; each Retryer has its own copy.
  64. func OnErrorFunc(bo Backoff, shouldRetry func(err error) bool) Retryer {
  65. return &errorRetryer{
  66. shouldRetry: shouldRetry,
  67. backoff: bo,
  68. }
  69. }
  70. type errorRetryer struct {
  71. backoff Backoff
  72. shouldRetry func(err error) bool
  73. }
  74. func (r *errorRetryer) Retry(err error) (time.Duration, bool) {
  75. if r.shouldRetry(err) {
  76. return r.backoff.Pause(), true
  77. }
  78. return 0, false
  79. }
  80. // OnCodes returns a Retryer that retries if and only if
  81. // the previous attempt returns a GRPC error whose error code is stored in cc.
  82. // Pause times between retries are specified by bo.
  83. //
  84. // bo is only used for its parameters; each Retryer has its own copy.
  85. func OnCodes(cc []codes.Code, bo Backoff) Retryer {
  86. return &boRetryer{
  87. backoff: bo,
  88. codes: append([]codes.Code(nil), cc...),
  89. }
  90. }
  91. type boRetryer struct {
  92. backoff Backoff
  93. codes []codes.Code
  94. }
  95. func (r *boRetryer) Retry(err error) (time.Duration, bool) {
  96. st, ok := status.FromError(err)
  97. if !ok {
  98. return 0, false
  99. }
  100. c := st.Code()
  101. for _, rc := range r.codes {
  102. if c == rc {
  103. return r.backoff.Pause(), true
  104. }
  105. }
  106. return 0, false
  107. }
  108. // OnHTTPCodes returns a Retryer that retries if and only if
  109. // the previous attempt returns a googleapi.Error whose status code is stored in
  110. // cc. Pause times between retries are specified by bo.
  111. //
  112. // bo is only used for its parameters; each Retryer has its own copy.
  113. func OnHTTPCodes(bo Backoff, cc ...int) Retryer {
  114. codes := make(map[int]bool, len(cc))
  115. for _, c := range cc {
  116. codes[c] = true
  117. }
  118. return &httpRetryer{
  119. backoff: bo,
  120. codes: codes,
  121. }
  122. }
  123. type httpRetryer struct {
  124. backoff Backoff
  125. codes map[int]bool
  126. }
  127. func (r *httpRetryer) Retry(err error) (time.Duration, bool) {
  128. var gerr *googleapi.Error
  129. if !errors.As(err, &gerr) {
  130. return 0, false
  131. }
  132. if r.codes[gerr.Code] {
  133. return r.backoff.Pause(), true
  134. }
  135. return 0, false
  136. }
  137. // Backoff implements exponential backoff. The wait time between retries is a
  138. // random value between 0 and the "retry period" - the time between retries. The
  139. // retry period starts at Initial and increases by the factor of Multiplier
  140. // every retry, but is capped at Max.
  141. //
  142. // Note: MaxNumRetries / RPCDeadline is specifically not provided. These should
  143. // be built on top of Backoff.
  144. type Backoff struct {
  145. // Initial is the initial value of the retry period, defaults to 1 second.
  146. Initial time.Duration
  147. // Max is the maximum value of the retry period, defaults to 30 seconds.
  148. Max time.Duration
  149. // Multiplier is the factor by which the retry period increases.
  150. // It should be greater than 1 and defaults to 2.
  151. Multiplier float64
  152. // cur is the current retry period.
  153. cur time.Duration
  154. }
  155. // Pause returns the next time.Duration that the caller should use to backoff.
  156. func (bo *Backoff) Pause() time.Duration {
  157. if bo.Initial == 0 {
  158. bo.Initial = time.Second
  159. }
  160. if bo.cur == 0 {
  161. bo.cur = bo.Initial
  162. }
  163. if bo.Max == 0 {
  164. bo.Max = 30 * time.Second
  165. }
  166. if bo.Multiplier < 1 {
  167. bo.Multiplier = 2
  168. }
  169. // Select a duration between 1ns and the current max. It might seem
  170. // counterintuitive to have so much jitter, but
  171. // https://www.awsarchitectureblog.com/2015/03/backoff.html argues that
  172. // that is the best strategy.
  173. d := time.Duration(1 + rand.Int63n(int64(bo.cur)))
  174. bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier)
  175. if bo.cur > bo.Max {
  176. bo.cur = bo.Max
  177. }
  178. return d
  179. }
  180. type grpcOpt []grpc.CallOption
  181. func (o grpcOpt) Resolve(s *CallSettings) {
  182. s.GRPC = o
  183. }
  184. type pathOpt struct {
  185. p string
  186. }
  187. func (p pathOpt) Resolve(s *CallSettings) {
  188. s.Path = p.p
  189. }
  190. type timeoutOpt struct {
  191. t time.Duration
  192. }
  193. func (t timeoutOpt) Resolve(s *CallSettings) {
  194. s.timeout = t.t
  195. }
  196. // WithPath applies a Path override to the HTTP-based APICall.
  197. //
  198. // This is for internal use only.
  199. func WithPath(p string) CallOption {
  200. return &pathOpt{p: p}
  201. }
  202. // WithGRPCOptions allows passing gRPC call options during client creation.
  203. func WithGRPCOptions(opt ...grpc.CallOption) CallOption {
  204. return grpcOpt(append([]grpc.CallOption(nil), opt...))
  205. }
  206. // WithTimeout is a convenience option for setting a context.WithTimeout on the
  207. // singular context.Context used for **all** APICall attempts. Calculated from
  208. // the start of the first APICall attempt.
  209. // If the context.Context provided to Invoke already has a Deadline set, that
  210. // will always be respected over the deadline calculated using this option.
  211. func WithTimeout(t time.Duration) CallOption {
  212. return &timeoutOpt{t: t}
  213. }
  214. // CallSettings allow fine-grained control over how calls are made.
  215. type CallSettings struct {
  216. // Retry returns a Retryer to be used to control retry logic of a method call.
  217. // If Retry is nil or the returned Retryer is nil, the call will not be retried.
  218. Retry func() Retryer
  219. // CallOptions to be forwarded to GRPC.
  220. GRPC []grpc.CallOption
  221. // Path is an HTTP override for an APICall.
  222. Path string
  223. // Timeout defines the amount of time that Invoke has to complete.
  224. // Unexported so it cannot be changed by the code in an APICall.
  225. timeout time.Duration
  226. }