diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f596bfc..69495c16b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/autorest/azure/async.go b/autorest/azure/async.go index c5fc511f6..5326f1fd3 100644 --- a/autorest/azure/async.go +++ b/autorest/azure/async.go @@ -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 { diff --git a/autorest/azure/async_test.go b/autorest/azure/async_test.go index 907be7a62..2c4818e4c 100644 --- a/autorest/azure/async_test.go +++ b/autorest/azure/async_test.go @@ -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") @@ -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))) diff --git a/autorest/version.go b/autorest/version.go index 28973b04d..713e23581 100644 --- a/autorest/version.go +++ b/autorest/version.go @@ -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",