@@ -15,44 +15,79 @@ def initialize(app, options = {})
1515 @logger = options [ :logger ]
1616 @error_codes = ( options . key? ( :retry_codes ) && options [ :retry_codes ] ) ? options [ :retry_codes ] : DEFAULT_ERROR_CODES
1717 @retry_on_exception = ( options . key? ( :retry_on_exception ) && options [ :retry_on_exception ] ) ? options [ :retry_on_exception ] : false
18+ @instrumentation = options [ :instrumentation ]
1819 end
1920
2021 def call ( env )
22+ # Duplicate env for retries but keep attempt counter persistent
2123 original_env = env . dup
24+ original_env [ :call_attempt ] = ( env [ :call_attempt ] || 0 )
25+
2226 exception_happened = false
27+ response = nil
28+
2329 if @retry_on_exception
2430 begin
2531 response = @app . call ( env )
26- rescue => e
32+ rescue => ex
2733 exception_happened = true
34+ exception = ex
2835 end
2936 else
37+ # Allow exceptions to propagate normally when not retrying
3038 response = @app . call ( env )
3139 end
3240
33- if exception_happened || @error_codes . include? ( response . env [ :status ] )
41+ if exception_happened
42+ original_env [ :call_attempt ] += 1
43+ seconds_left = DEFAULT_RETRY_AFTER . to_i
44+ @logger &.warn "An exception happened, waiting #{ seconds_left } seconds... #{ exception } "
45+ instrument_retry ( original_env , "exception" , seconds_left )
46+ sleep_with_logging ( seconds_left )
47+ return @app . call ( original_env )
48+ end
3449
35- if exception_happened
36- seconds_left = DEFAULT_RETRY_AFTER . to_i
37- @logger &.warn "An exception happened, waiting #{ seconds_left } seconds... #{ e } "
38- else
39- seconds_left = ( response . env [ :response_headers ] [ :retry_after ] || DEFAULT_RETRY_AFTER ) . to_i
40- end
50+ # Retry once if response has a retryable error code
51+ if response && @error_codes . include? ( response . env [ :status ] )
52+ original_env [ :call_attempt ] += 1
53+ seconds_left = ( response . env [ :response_headers ] [ :retry_after ] || DEFAULT_RETRY_AFTER ) . to_i
54+ @logger &.warn "You may have been rate limited. Retrying in #{ seconds_left } seconds..."
55+ instrument_retry ( original_env , ( response . env [ :status ] == 429 ) ? "rate_limited" : "server_error" , seconds_left )
56+ sleep_with_logging ( seconds_left )
57+ response = @app . call ( original_env )
58+ end
4159
42- @logger &.warn "You have been rate limited. Retrying in #{ seconds_left } seconds..."
60+ response
61+ end
4362
44- seconds_left . times do |i |
45- sleep 1
46- time_left = seconds_left - i
47- @logger &.warn "#{ time_left } ..." if time_left > 0 && time_left % 5 == 0
48- end
63+ private
4964
50- @logger &.warn ""
65+ def instrument_retry ( env , reason , delay )
66+ return unless @instrumentation
5167
52- @app . call ( original_env )
53- else
54- response
68+ begin
69+ @instrumentation . instrument (
70+ "zendesk.retry" ,
71+ {
72+ attempt : env [ :call_attempt ] ,
73+ endpoint : env [ :url ] &.path ,
74+ method : env [ :method ] ,
75+ reason : reason ,
76+ delay : delay
77+ }
78+ )
79+ rescue => e
80+ @logger &.debug ( "zendesk.retry instrumentation failed: #{ e . message } " )
81+ end
82+ end
83+
84+ def sleep_with_logging ( seconds_left )
85+ seconds_left . times do |i |
86+ sleep 1
87+ time_left = seconds_left - i
88+ @logger &.warn "#{ time_left } ..." if time_left > 0 && time_left % 5 == 0
5589 end
90+ @logger &.warn "" if seconds_left > 0
5691 end
5792 end
5893 end
0 commit comments