Skip to content

feat: add CURLRequest retry option#10157

Open
memleakd wants to merge 5 commits intocodeigniter4:4.8from
memleakd:feat/curlrequest-retry
Open

feat: add CURLRequest retry option#10157
memleakd wants to merge 5 commits intocodeigniter4:4.8from
memleakd:feat/curlrequest-retry

Conversation

@memleakd
Copy link
Copy Markdown
Contributor

@memleakd memleakd commented May 4, 2026

Description

This PR proposes adding retry option to CURLRequest.

Modern applications often depend on external APIs for payments, notifications, search, analytics, AI services, webhooks, and internal service-to-service calls. These requests can fail temporarily because of rate limits, short outages, gateway errors, DNS/connectivity issues, or upstream overload. Today, users need to write this retry flow manually around CURLRequest.

This PR adds a small, opt-in retry layer directly to CURLRequest so common transient failures can be handled in a framework-native way.

For simple cases, users can pass an integer retry count:

$response = $client->request('GET', 'https://api.example.com/items', [
    'retry' => 3,
]);

For more control, users can pass an array:

$response = $client->request('GET', 'https://api.example.com/items', [
    'retry' => [
        'max_retries'         => 3,
        'delay'               => [100, 500, 1000],
        'max_delay'           => 30000,
        'status_codes'        => [429, 500, 502, 503, 504],
        'curl_errors'         => true,
        'respect_retry_after' => true,
    ],
]);

The feature is intentionally contained within CURLRequest and does not change existing request behavior unless retry is explicitly configured.

Behavior and Defaults

  • retry may be an integer retry count or an array of retry settings.
  • max_retries is the number of retries after the initial request.
  • delay is in milliseconds and may be a fixed integer or a simple backoff array.
  • status_codes defaults to [429, 503, 504], covering rate limiting and service unavailability without retrying every server error by default.
  • respect_retry_after defaults to true, so valid Retry-After headers are honored.
  • max_delay defaults to 30000 ms to avoid unexpectedly long blocking waits in synchronous PHP processes.
  • max_delay caps both configured delays and valid Retry-After values. Users can set it to 0 for no cap.
  • curl_errors defaults to false; transient cURL/network retries are available explicitly with curl_errors => true.
  • When http_errors is enabled, retryable HTTP errors are retried first. If retries are exhausted, the final HTTP error still throws as expected.
  • Non-idempotent requests such as POST and PATCH are not blocked, but the docs warn that the remote server may receive the request more than once.

Design Notes

The API is deliberately small:

  • Simple users get 'retry' => 3.
  • Advanced users can configure attempts, delay, max delay, status codes, cURL error retries, and Retry-After behavior.
  • Retry delays are synchronous because CURLRequest itself is synchronous.
  • The default max_delay protects PHP-FPM workers, queue workers, and long-running processes from accidentally sleeping for a very long server-supplied Retry-After.
  • cURL error retries are opt-in because network-level failures can be ambiguous for non-idempotent requests.
  • Method-based blocking is avoided because only the application knows whether a request is safe to retry, for example when using idempotency keys.

Documentation

The user guide documents:

  • Simple integer retry usage.
  • Advanced array configuration.
  • All retry settings and defaults.
  • Retry-After priority and max delay behavior.
  • Interaction with http_errors.
  • Which transient cURL errors may be retried.
  • Blocking behavior in synchronous PHP.
  • Caution for non-idempotent requests.

Tests

Tests cover:

  • Integer retry shorthand.
  • Default retryable status codes.
  • Custom retryable status codes.
  • Non-retryable status codes.
  • Disabled retry handling.
  • Fixed and backoff delays.
  • Negative delay normalization.
  • Retry-After seconds and HTTP-date handling.
  • Default max_delay cap.
  • Disabling Retry-After handling.
  • Exhausted retries with http_errors enabled.
  • Exhausted retries with http_errors disabled.
  • Default non-retry behavior for cURL errors.
  • Opt-in retry behavior for transient cURL errors.
  • Non-transient cURL errors.

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

- Add retry support for failed HTTP responses
- Support configurable attempts, delays, status codes, and Retry-After
- Add opt-in retries for transient cURL errors
- Document retry behavior and safety considerations
- Add focused CURLRequest retry tests

Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
@github-actions github-actions Bot added the 4.8 PRs that target the `4.8` branch. label May 4, 2026
Copy link
Copy Markdown
Contributor

@patel-vansh patel-vansh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this feature is a nice addition. Thanks! I always wanted a similar feature. Just few thoughts

Comment thread system/HTTP/CURLRequest.php Outdated
Comment thread system/HTTP/CURLRequest.php Outdated
- Add 504 Gateway Timeout to default retryable status codes
- Reuse stored cURL error number when throwing
- Add coverage for default 504 retry behavior

Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Comment thread system/HTTP/CURLRequest.php Outdated
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Comment thread system/HTTP/CURLRequest.php Outdated
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Copy link
Copy Markdown
Member

@michalsn michalsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I like it. The user guide is clear enough to me.

Copy link
Copy Markdown
Contributor

@patel-vansh patel-vansh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM too!

Comment thread system/HTTP/CURLRequest.php Outdated
Comment thread system/HTTP/CURLRequest.php Outdated
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@datamweb datamweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.8 PRs that target the `4.8` branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants