@@ -292,6 +292,41 @@ func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) {
292292 return 0 , false
293293}
294294
295+ // isBeforeContextDeadline reports whether the non-zero Time t is
296+ // before ctx's deadline. If ctx does not have a deadline, it
297+ // always reports true (the deadline is considered infinite).
298+ func isBeforeContextDeadline (t time.Time , ctx context.Context ) bool {
299+ d , ok := ctx .Deadline ()
300+ if ! ok {
301+ return true
302+ }
303+ return t .Before (d )
304+ }
305+
306+ // bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func
307+ // to handle timeouts etc. It wraps an existing io.ReadCloser.
308+ type bodyWithTimeout struct {
309+ stop func () // stops the time.Timer waiting to cancel the request
310+ rc io.ReadCloser
311+ }
312+
313+ func (b * bodyWithTimeout ) Read (p []byte ) (n int , err error ) {
314+ n , err = b .rc .Read (p )
315+ if err == nil {
316+ return n , nil
317+ }
318+ if err == io .EOF {
319+ return n , err
320+ }
321+ return n , err
322+ }
323+
324+ func (b * bodyWithTimeout ) Close () error {
325+ err := b .rc .Close ()
326+ b .stop ()
327+ return err
328+ }
329+
295330func retryDelay (res * http.Response , retryCount int ) time.Duration {
296331 // If the API asks us to wait a certain amount of time (and it's a reasonable amount),
297332 // just do what it says.
@@ -353,12 +388,17 @@ func (cfg *RequestConfig) Execute() (err error) {
353388 shouldSendRetryCount := cfg .Request .Header .Get ("X-Stainless-Retry-Count" ) == "0"
354389
355390 var res * http.Response
391+ var cancel context.CancelFunc
356392 for retryCount := 0 ; retryCount <= cfg .MaxRetries ; retryCount += 1 {
357393 ctx := cfg .Request .Context ()
358- if cfg .RequestTimeout != time .Duration (0 ) {
359- var cancel context.CancelFunc
394+ if cfg .RequestTimeout != time .Duration (0 ) && isBeforeContextDeadline (time .Now ().Add (cfg .RequestTimeout ), ctx ) {
360395 ctx , cancel = context .WithTimeout (ctx , cfg .RequestTimeout )
361- defer cancel ()
396+ defer func () {
397+ // The cancel function is nil if it was handed off to be handled in a different scope.
398+ if cancel != nil {
399+ cancel ()
400+ }
401+ }()
362402 }
363403
364404 req := cfg .Request .Clone (ctx )
@@ -426,10 +466,15 @@ func (cfg *RequestConfig) Execute() (err error) {
426466 return & aerr
427467 }
428468
429- if cfg .ResponseBodyInto == nil {
430- return nil
431- }
432- if _ , ok := cfg .ResponseBodyInto .(* * http.Response ); ok {
469+ _ , intoCustomResponseBody := cfg .ResponseBodyInto .(* * http.Response )
470+ if cfg .ResponseBodyInto == nil || intoCustomResponseBody {
471+ // We aren't reading the response body in this scope, but whoever is will need the
472+ // cancel func from the context to observe request timeouts.
473+ // Put the cancel function in the response body so it can be handled elsewhere.
474+ if cancel != nil {
475+ res .Body = & bodyWithTimeout {rc : res .Body , stop : cancel }
476+ cancel = nil
477+ }
433478 return nil
434479 }
435480
0 commit comments