Repository: go
Issue: x/crypto/acme: ACME client's internal retry implementation results in hanging retries on 429s
Labels: NeedsInvestigation

<!--
Please answer these questions before submitting your issue. Thanks!
For questions please use one of our forums: https://github.com/golang/go/wiki/Questions
-->
### What version of Go are you using (`go version`)?
<pre>
$ go version
go version go1.14.4 darwin/amd64
</pre>
### Does this issue reproduce with the latest release?
Yes
### What operating system and processor architecture are you using (`go env`)?
<details><summary><code>go env</code> Output</summary><br><pre>
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN="/Users/viola/go/bin"
GOCACHE="/Users/viola/Library/Caches/go-build"
GOENV="/Users/viola/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/viola/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.14.4/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.14.4/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/viola/crypto/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/0s/nyt41p_j69d8vzq1qfkg5nrh0000gn/T/go-build384678003=/tmp/go-build -gno-record-gcc-switches -fno-common"
</details></pre>

### What did you do?

Start off with triggering a rate-limit, to get a 429 from Let's Encrypt. Here's how I triggered one by going over [the default 5 per week duplicate certificate limit set by Let's Encrypt](https://letsencrypt.org/docs/rate-limits/) :

* Call `AuthorizeOrder` with a valid AuthzID identifier.
* Repeat the request 6 times.
* On the 6th attempt, the request winds up hanging with a much longer execution. It wound up hitting a timeout set up an upstream caller at 45 seconds.
* Unless [the context is canceled or timed-out](https://github.com/golang/crypto/blob/4663e185863a1aee50d0486b326769f0bd22eb30/acme/http.go#L127-L128), or a ["non-retriable error status"](https://github.com/golang/crypto/blob/4663e185863a1aee50d0486b326769f0bd22eb30/acme/http.go#L299-L303) is received, retries are indefinite. In this case, the client never returns, because 429 is a retriable error code, and this will be a somewhat terminal state (for the rest of the week).
* Here's a look at the 429 that the client was receiving and retrying on in this example.

```
429 urn:ietf:params:acme:error:rateLimited: Error creating new order :: too many certificates already issued for exact set of domains: violababola.best: see https://letsencrypt.org/docs/rate-limits/
```

### What did you expect to see?
* Fail fast and not to retry on the [client side error](https://github.com/golang/crypto/blob/master/acme/http.go#L302) => `http.StatusTooManyRequests` since this is a not recoverable response error code. At least, the facility to configure this.
* Also, that there is some bound on the number of retries.

### What did you see instead?

Retries on the [client side error](https://github.com/golang/crypto/blob/master/acme/http.go#L302) => `http.StatusTooManyRequests`
It seems like `http.StatusTooManyRequests` is something that should not be considered as a retriable response error code as it's not recoverable.

### Related PR Fix
* This [PR](https://github.com/golang/crypto/pull/149) introduces a custom `ShouldRetry` func option that can be set on the ACME client to allow the default set of retriable response error codes to be overridden. This will keep the current behaviour backwards compatible, but provide more flexible retry configuration.