Skip to content

Commit

Permalink
Initial sleep on Retry-After (#539)
Browse files Browse the repository at this point in the history
If the initial response to an LRO contains a Retry-After header, sleep
for the specified duration before beginning to poll.
  • Loading branch information
jhendrixMSFT committed Jul 9, 2020
1 parent 9210cb7 commit 596f536
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## v14.2.1

- In `Future.WaitForCompletionRef()`, if the initial async response includes a `Retry-After` header, sleep for the specified amount of time before starting to poll.

## v14.2.0

- Added package comment to make `github.com/Azure/go-autorest` importable.
Expand Down
8 changes: 7 additions & 1 deletion autorest/azure/async.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,13 @@ func (f *Future) WaitForCompletionRef(ctx context.Context, client autorest.Clien
cancelCtx, cancel = context.WithTimeout(ctx, d)
defer cancel()
}

// if the initial response has a Retry-After, sleep for the specified amount of time before starting to poll
if delay, ok := f.GetPollingDelay(); ok {
if delayElapsed := autorest.DelayForBackoff(delay, 0, cancelCtx.Done()); !delayElapsed {
err = cancelCtx.Err()
return
}
}
done, err := f.DoneWithContext(ctx, client)
for attempts := 0; !done; done, err = f.DoneWithContext(ctx, client) {
if attempts >= client.RetryAttempts {
Expand Down
40 changes: 40 additions & 0 deletions autorest/azure/async_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,39 @@ func TestFuture_WaitForCompletionRef(t *testing.T) {
autorest.ByClosing())
}

func TestFuture_WaitForCompletionRefWithRetryAfter(t *testing.T) {
r2 := newOperationResourceResponse("busy")
r3 := newOperationResourceResponse(operationSucceeded)

sender := mocks.NewSender()
sender.AppendAndRepeatResponse(r2, 2)
sender.AppendResponse(r3)
client := autorest.Client{
PollingDelay: 1 * time.Second,
PollingDuration: autorest.DefaultPollingDuration,
RetryAttempts: autorest.DefaultRetryAttempts,
RetryDuration: 1 * time.Second,
Sender: sender,
}

future, err := NewFutureFromResponse(newSimpleAsyncRespWithRetryAfter())
if err != nil {
t.Fatalf("failed to create future: %v", err)
}

err = future.WaitForCompletionRef(context.Background(), client)
if err != nil {
t.Fatalf("WaitForCompletion returned non-nil error")
}

if sender.Attempts() < sender.NumResponses() {
t.Fatalf("stopped polling before receiving a terminated OperationResource")
}

autorest.Respond(future.Response(),
autorest.ByClosing())
}

func TestFuture_WaitForCompletionTimedOut(t *testing.T) {
r2 := newProvisioningStatusResponse("busy")

Expand Down Expand Up @@ -989,6 +1022,13 @@ func newSimpleAsyncResp() *http.Response {
return r
}

func newSimpleAsyncRespWithRetryAfter() *http.Response {
r := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusCreated, mocks.NewBody(fmt.Sprintf(operationResourceFormat, operationInProgress)))
mocks.SetResponseHeader(r, headerAsyncOperation, mocks.TestAzureAsyncURL)
mocks.SetResponseHeader(r, autorest.HeaderRetryAfter, "1")
return r
}

// creates a simple LRO response, POST/201 with Location header
func newSimpleLocationResp() *http.Response {
r := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusCreated, mocks.NewBody(fmt.Sprintf(pollingStateFormat, operationInProgress)))
Expand Down
2 changes: 1 addition & 1 deletion autorest/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"runtime"
)

const number = "v14.2.0"
const number = "v14.2.1"

var (
userAgent = fmt.Sprintf("Go/%s (%s-%s) go-autorest/%s",
Expand Down

0 comments on commit 596f536

Please sign in to comment.