feat(postgrest): configurable auto-retry and request timeout#1560
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the postgrest Dart client to make its automatic retry behavior configurable (retry count + retryable status codes) and adds an optional per-request HTTP request timeout, surfacing the configuration both on PostgrestClient and through PostgrestClientOptions (so it can be configured via SupabaseClient).
Changes:
- Added configurable
retryCount,retryableStatusCodes, andrequestTimeouttoPostgrestClientand threaded these through the various builder layers. - Extended
PostgrestBuilder.retry(...)to support per-request retry count overrides (count) in addition to enabling/disabling retries. - Updated tests and compliance matrix entries to cover/reflect the new configuration and timeout behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk-compliance.yaml | Marks database auto-retry and request timeout as implemented and lists related symbols. |
| packages/supabase/lib/src/supabase_client.dart | Passes PostgREST retry/timeout options from SupabaseClient into PostgrestClient. |
| packages/supabase/lib/src/supabase_client_options.dart | Adds PostgREST retry/timeout configuration to PostgrestClientOptions. |
| packages/postgrest/test/retry_test.dart | Adds test coverage for configurable retry count, custom status codes, and request timeout behavior. |
| packages/postgrest/lib/src/raw_postgrest_builder.dart | Propagates retry/timeout configuration through raw builder wrapping/copying. |
| packages/postgrest/lib/src/postgrest.dart | Adds retry/timeout fields to PostgrestClient and forwards them into builders. |
| packages/postgrest/lib/src/postgrest_transform_builder.dart | Updates .retry(...) override to support optional count. |
| packages/postgrest/lib/src/postgrest_rpc_builder.dart | Threads retry/timeout configuration into RPC builder construction. |
| packages/postgrest/lib/src/postgrest_query_builder.dart | Threads retry/timeout config and updates .retry(...) to allow per-request count override. |
| packages/postgrest/lib/src/postgrest_filter_builder.dart | Updates .retry(...) override to support optional count. |
| packages/postgrest/lib/src/postgrest_builder.dart | Implements configurable retry count/status codes and applies per-request timeout logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Actually with http 1.5.0 aborting requests is supported so the timeout option might be reconsidered. https://pub.dev/packages/http#aborting-requests See #1368 for manual abort. |
Oooh they finally added that, I know it has been an open issue for years. 😅 Edit: forgot that you already have #1368 open, I'll close 1577 and let 1368 deal with it then |
b854cc0 to
756f0f0
Compare
756f0f0 to
1099735
Compare
Vinzent03
left a comment
There was a problem hiding this comment.
What do you think of extending the http RequestAbortedException exception with two distinct subclasses for either manual abort and and timeout abort? This would allow the user to easier differentiate whether the abort came from a timeout or the user clicked a button in their ui. Surely, this could also be implemented by the user manually, but including it in the library should be easier for the user.
b06a401 to
da7b5a6
Compare
…eout Makes the automatic retry behavior configurable through retryEnabled, retryCount and retryableStatusCodes on PostgrestClient and PostgrestClientOptions, and per request via PostgrestBuilder.retry (which now also accepts a count). All defaults preserve the previous behavior. Rebased on top of the request abort feature (#1368) and now builds the request timeout on top of it. A configured requestTimeout aborts the in-flight request through AbortableRequest and stops any pending retries instead of merely abandoning the future like Future.timeout would. The retry loop rethrows RequestAbortedException so an abort always stops retries, and requestTimeout and abortSignal are threaded through the reconstructing builders so they survive withConverter and count chains.
da7b5a6 to
b03bc95
Compare
I don't think we'd need to create new exceptions for that but it would indeed be good for the user to be able to separate them, I think a better way is if we just synthesize a When investigating this I also noticed that the timeout was wrapping the retries, so timed out requests didn't retry, which obviously is wrong. Fixed now. |
|
I like just using the existing |
Summary
Makes the PostgREST client's automatic retry behavior configurable and adds an optional per-request timeout that actually cancels a stalled attempt. Previously the retry count and the retryable conditions were hard-coded, and there was no way to bound the duration of a request. Rebased on top of the request abort feature (#1368), whose
AbortableRequestsupport is what makes a real timeout possible.All defaults preserve the current behavior, so the change is non-breaking.
Configurable auto-retry
New optional configuration on
PostgrestClient(and per request viaPostgrestBuilder.retry):retryEnabled(defaulttrue): whether retries are performed for retryable GET and HEAD requests.retryCount(default3): number of retry attempts for a retryable request. A negative value throws anArgumentError.retryableStatusCodes(default{503, 520}): HTTP status codes that trigger a retry. The set is copied defensively so later mutation of the caller's set does not change retry behavior.PostgrestBuilder.retry({bool enabled = true, int? count, Duration? requestTimeout}): the per-request override now also accepts acountand arequestTimeout. AnullrequestTimeoutkeeps the client-level timeout.The default retryable status codes are exposed as
PostgrestClient.defaultRetryableStatusCodes, since the client is where retries are configured.Request timeout
requestTimeoutis available onPostgrestClientandPostgrestClientOptions, and can be overridden per request throughPostgrestBuilder.retry. It bounds how long a single request attempt may take and is built on top of the abort mechanism from #1368, so it actually cancels a stalled attempt throughAbortableRequestrather than leaving it running the wayFuture.timeoutwould. Whennull(the default) no timeout is applied.It is a per-attempt timeout, not a whole-operation deadline: a timed-out attempt is retried like any other transient failure, and a
TimeoutExceptionis thrown only once the retries are exhausted. A caller-providedPostgrestBuilder.abortSignal, by contrast, cancels the request outright and stops retrying immediately with aRequestAbortedException. Internally both share the same abort trigger per attempt; the source that fired is tracked so a timeout surfaces asTimeoutExceptionwhile a manual abort keepsRequestAbortedException.If you need a hard deadline across all retries, wrap the returned future with
.timeout(...)(PostgrestBuilderimplementsFuture).Configuration through SupabaseClient
retryEnabled,retryCount,retryableStatusCodes, andrequestTimeoutare surfaced throughPostgrestClientOptionsso they can be set on the top-levelSupabaseClient.Builder internals
The changes above required threading more state (retry settings,
requestTimeout,abortSignal) through the immutable builder chain, so the copy internals were cleaned up along the way. These are internal-only and non-breaking:_RetryConfigvalue instead of separate fields, which also fixesResponsePostgrestBuildersilently dropping the retry count and status codes oncount()chains.requestTimeout,abortSignal) is held in a single immutable_RequestConfigvalue with acopyWith. The typed builders now share and rewrap one config instance rather than re-listing every field, sowithConverter, the transform and query chains, and the wrapper builders no longer drop or duplicate request state.Compliance matrix
database.configuration.auto_retrybecomesimplemented.database.configuration.request_timeoutbecomesimplemented, now that request cancellation exists to back it.Tests
Extended
packages/postgrest/test/retry_test.dartwith cases for configurable retry count, per-request count override,retryCount: 0, negativeretryCountvalidation, and custom retryable status codes. For the timeout: a timed-out attempt is retried rather than hard-stopped, retries recover once an attempt completes within the timeout, a request that finishes in time is not timed out, a per-requestretry(requestTimeout:)override times out, and a manualabortSignalstops retrying immediately.SDK-1270