@@ -25,22 +25,43 @@ def use_write_timeout?
2525 end
2626
2727 # Same as the original start, but adds a checkpoint for starting HTTP
28- # requests and sets network timeouts to the remaining time
28+ # requests and sets network timeouts to the remaining time.
29+ #
30+ # Also applies the same logic to begin_transport, which is called on
31+ # every HTTP attempt including internal retries. Net::HTTP#transport_request
32+ # silently retries idempotent requests (GET, HEAD, PUT, DELETE, OPTIONS,
33+ # TRACE) up to max_retries (default 1) on transient errors like
34+ # Net::ReadTimeout. Without re-applying the cutoff in begin_transport,
35+ # the retry path goes through #connect (not #start), reuses the original
36+ # read_timeout, and effectively doubles the deadline you set.
2937 #
3038 # @method start
39+ # @method begin_transport
3140 # @see Net::HTTP#start
41+ # @see Net::HTTP#begin_transport
3242 module_eval ( <<~RUBY , __FILE__ , __LINE__ + 1 )
3343 def start
34- if (cutoff = Cutoff.current) && cutoff.selected?(:net_http)
35- remaining = cutoff.seconds_remaining
36- #{ gen_timeout_method ( 'open_timeout' ) }
37- #{ gen_timeout_method ( 'read_timeout' ) }
38- #{ gen_timeout_method ( 'write_timeout' ) if use_write_timeout? }
39- #{ gen_timeout_method ( 'continue_timeout' ) }
40- Cutoff.checkpoint!(:net_http)
41- end
44+ _cutoff_apply_to_net_http
4245 super
4346 end
47+
48+ def begin_transport(req)
49+ _cutoff_apply_to_net_http
50+ super
51+ end
52+
53+ private
54+
55+ def _cutoff_apply_to_net_http
56+ return unless (cutoff = Cutoff.current) && cutoff.selected?(:net_http)
57+
58+ remaining = cutoff.seconds_remaining
59+ #{ gen_timeout_method ( 'open_timeout' ) }
60+ #{ gen_timeout_method ( 'read_timeout' ) }
61+ #{ gen_timeout_method ( 'write_timeout' ) if use_write_timeout? }
62+ #{ gen_timeout_method ( 'continue_timeout' ) }
63+ Cutoff.checkpoint!(:net_http)
64+ end
4465 RUBY
4566 end
4667 end
0 commit comments