Skip to content

Commit 337c068

Browse files
authored
Merge pull request #30 from dbt-labs/harry/cherry_pick_context_deadline_exceeded_fix
Cherry-pick a change to mitigate the context deadline exceeded error
2 parents bb1a3f7 + 6a859f2 commit 337c068

4 files changed

Lines changed: 354 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Upcoming Release
44

5+
Bug fixes:
6+
7+
- Handle HTTP307 & 308 in drivers to achieve better resiliency to backend errors (snowflakedb/gosnowflake#1616).
8+
9+
- Fix unsafe reflection of nil pointer on DECFLOAT func in bind uploader (snowflakedb/gosnowflake#1604).
510
- Added temporary download files cleanup (snowflakedb/gosnowflake#1577)
611
- Marked fields as deprecated (snowflakedb/gosnowflake#1556)
712
- Exposed `QueryStatus` from `SnowflakeResult` and `SnowflakeRows` in `GetStatus()` function (snowflakedb/gosnowflake#1556)

retry.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package gosnowflake
33
import (
44
"bytes"
55
"context"
6-
"errors"
76
"fmt"
87
"io"
98
"math"
@@ -323,7 +322,7 @@ func (r *retryHTTP) execute() (res *http.Response, err error) {
323322
}
324323
res, err = r.client.Do(req)
325324
// check if it can retry.
326-
retryable, err := isRetryableError(req, res, err)
325+
retryable, err := isRetryableError(r.ctx, req, res, err)
327326
if !retryable {
328327
return res, err
329328
}
@@ -387,11 +386,11 @@ func (r *retryHTTP) execute() (res *http.Response, err error) {
387386
}
388387
}
389388

390-
func isRetryableError(req *http.Request, res *http.Response, err error) (bool, error) {
389+
func isRetryableError(ctx context.Context, req *http.Request, res *http.Response, err error) (bool, error) {
390+
if ctx.Err() != nil {
391+
return false, ctx.Err()
392+
}
391393
if err != nil && res == nil { // Failed http connection. Most probably client timeout.
392-
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
393-
return false, err
394-
}
395394
return true, err
396395
}
397396
if res == nil || req == nil {

retry_test.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gosnowflake
33
import (
44
"bytes"
55
"context"
6+
"database/sql"
67
"fmt"
78
"io"
89
"net/http"
@@ -486,70 +487,84 @@ func TestLoginRetry429(t *testing.T) {
486487
}
487488

488489
func TestIsRetryable(t *testing.T) {
490+
deadLineCtx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
491+
defer cancel()
492+
time.Sleep(2 * time.Nanosecond)
493+
489494
tcs := []struct {
495+
ctx context.Context
490496
req *http.Request
491497
res *http.Response
492498
err error
493499
expected bool
494500
}{
495501
{
502+
ctx: context.Background(),
496503
req: nil,
497504
res: nil,
498505
err: nil,
499506
expected: false,
500507
},
501508
{
509+
ctx: context.Background(),
502510
req: nil,
503511
res: &http.Response{StatusCode: http.StatusBadRequest},
504512
err: nil,
505513
expected: false,
506514
},
507515
{
516+
ctx: context.Background(),
508517
req: &http.Request{URL: &url.URL{Path: loginRequestPath}},
509518
res: nil,
510519
err: nil,
511520
expected: false,
512521
},
513522
{
523+
ctx: context.Background(),
514524
req: &http.Request{URL: &url.URL{Path: loginRequestPath}},
515525
res: &http.Response{StatusCode: http.StatusNotFound},
516526
expected: false,
517527
},
518528
{
519-
req: &http.Request{URL: &url.URL{Path: loginRequestPath}},
520-
res: nil,
521-
err: &url.Error{Err: context.Canceled},
522-
expected: false,
523-
},
524-
{
529+
ctx: context.Background(),
525530
req: &http.Request{URL: &url.URL{Path: loginRequestPath}},
526531
res: nil,
527532
err: &url.Error{Err: context.DeadlineExceeded},
528-
expected: false,
533+
expected: true,
529534
},
530535
{
536+
ctx: context.Background(),
531537
req: &http.Request{URL: &url.URL{Path: loginRequestPath}},
532538
res: nil,
533539
err: errUnknownError(),
534540
expected: true,
535541
},
536542
{
543+
ctx: context.Background(),
537544
req: &http.Request{URL: &url.URL{Path: loginRequestPath}},
538545
res: &http.Response{StatusCode: http.StatusTooManyRequests},
539546
err: nil,
540547
expected: true,
541548
},
542549
{
550+
ctx: deadLineCtx,
551+
req: &http.Request{URL: &url.URL{Path: loginRequestPath}},
552+
res: nil,
553+
err: &url.Error{Err: context.DeadlineExceeded},
554+
expected: false,
555+
},
556+
{
557+
ctx: deadLineCtx,
543558
req: &http.Request{URL: &url.URL{Path: queryRequestPath}},
544-
res: &http.Response{StatusCode: http.StatusServiceUnavailable},
545-
err: nil,
546-
expected: true,
559+
res: nil,
560+
err: &url.Error{Err: context.DeadlineExceeded},
561+
expected: false,
547562
},
548563
}
549564

550565
for _, tc := range tcs {
551566
t.Run(fmt.Sprintf("req %v, resp %v", tc.req, tc.res), func(t *testing.T) {
552-
result, _ := isRetryableError(tc.req, tc.res, tc.err)
567+
result, _ := isRetryableError(tc.ctx, tc.req, tc.res, tc.err)
553568
if result != tc.expected {
554569
t.Fatalf("expected %v, got %v; request: %v, response: %v", tc.expected, result, tc.req, tc.res)
555570
}
@@ -673,3 +688,12 @@ func TestCalculateRetryWaitForNonAuthRequests(t *testing.T) {
673688
})
674689
}
675690
}
691+
692+
func TestRedirectRetry(t *testing.T) {
693+
wiremock.registerMappings(t, newWiremockMapping("retry/redirection_retry_workflow.json"))
694+
cfg := wiremock.connectionConfig()
695+
cfg.ClientTimeout = 3 * time.Second
696+
connector := NewConnector(SnowflakeDriver{}, *cfg)
697+
db := sql.OpenDB(connector)
698+
runSmokeQuery(t, db)
699+
}

0 commit comments

Comments
 (0)