Skip to content

feat: allow request timeouts to be configured via ClientOptions - #339

Open
cheekychops wants to merge 1 commit into
recurly:v3-v2021-02-25from
cheekychops:feat/configurable-timeouts
Open

feat: allow request timeouts to be configured via ClientOptions#339
cheekychops wants to merge 1 commit into
recurly:v3-v2021-02-25from
cheekychops:feat/configurable-timeouts

Conversation

@cheekychops

Copy link
Copy Markdown

Problem

BaseClient builds its OkHttpClient with no timeout overrides, so every caller gets the OkHttp defaults — 10s connect, 10s read, 10s write, and no overall call timeout — with no supported way to change them:

  • Client exposes only Client(apiKey) and Client(apiKey, ClientOptions)
  • ClientOptions carries the region and nothing else
  • newHttpClient is private static
  • the BaseClient constructors that do accept an OkHttpClient are protected, and a Client subclass cannot reach them through super(...)

So the only routes available today are reflection over BaseClient's private final OkHttpClient client field, or vendoring the SDK. Neither is something we want in a payments integration.

Why 10s read is the painful one

For an integration whose Recurly calls are mostly writes, a 10s read timeout converts a slow-but-successful response into a failure whose outcome the caller cannot determine. A createSubscription that times out at 10s may well have been applied at Recurly — so the caller cannot simply retry, and instead has to reconcile by reading back the account's subscriptions and matching them against what it asked for.

We have production evidence of exactly that: a recent burst of timed-out subscription creates all took 10.7–10.8s, and in every case the write had in fact landed — confirmed by Recurly's own new_subscription and successful_payment webhooks arriving seconds later. The customer was charged and shown an error. Being able to raise the read timeout is a far smaller thing to own than the reconciliation logic it forces.

Change

ClientOptions gains optional connectTimeout, readTimeout, writeTimeout and callTimeout (java.time.Duration), and newHttpClient applies each one that is set:

ClientOptions options = new ClientOptions();
options.setReadTimeout(Duration.ofSeconds(30));
Client client = new Client(apiKey, options);

Anything left unset behaves exactly as before, so this is source- and behaviour-compatible. newHttpClient becomes package-private so the tests can assert on the client it builds; it is not part of the public surface either way.

Tests

Four new cases in BaseClientTest:

  • defaults unchanged when nothing is configured (pins the current 10s/10s/10s and no call timeout)
  • all four timeouts applied when set
  • timeouts applied individually, without disturbing the others
  • a configured timeout surviving construction through the public Client(apiKey, ClientOptions) constructor

mvn test is green: 48 tests, 0 failures, with the existing 26 BaseClientTest cases untouched. Reverting the wiring in newHttpClient fails exactly the three tests that assert configured values, so they do bind to the change.

I could not run ./scripts/formatbin/google-java-format-1.35.0-all-deps.jar is not present in the repo — so the new code follows the surrounding style by hand and stays within 100 columns. Happy to reformat if CI disagrees.

Notes

BaseClient builds its OkHttpClient with no timeout overrides, so every
caller gets the OkHttp defaults of 10s connect, 10s read and 10s write,
with no way to change them: Client exposes only (apiKey) and (apiKey,
ClientOptions), ClientOptions carries the region, newHttpClient is private
static, and the BaseClient constructors that accept an OkHttpClient are
protected and unreachable from a Client subclass.

For an integration whose calls are mostly writes, a 10s read timeout turns
a slow-but-successful Recurly response into a failure whose outcome the
caller cannot determine. A subscription create that times out may well have
been applied, so the caller has to reconcile by reading back rather than
retrying, and the safest version of that is a lot of machinery to carry for
something a timeout setting would avoid.

ClientOptions now takes optional connect, read, write and call timeouts,
and newHttpClient applies each one that is set. Anything left unset keeps
the current behaviour exactly, so this is source and behaviour compatible;
the existing tests are unchanged and still pass.

newHttpClient becomes package-private so the tests can assert on the client
it builds. Four cases: defaults unchanged when nothing is configured, all
four timeouts applied when set, timeouts applied individually without
disturbing the others, and a configured timeout surviving construction
through the public Client constructor.
@cheekychops cheekychops closed this Aug 5, 2026
@cheekychops cheekychops reopened this Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant