All guides
No guide matches your search.
Choosing policies
See which protection answers which problem, how your classifier decides what counts as a failure, and which options you must size yourself.
On this page
Reqtide ships no defaults for timeouts, limits, windows or delays, and these guides recommend no numbers. The right values depend on your dependency, your traffic and your risk.
Every code block in the protection guides comes from examples/09-choosing-policies.php (opens in a new tab) or a sibling example. A script runs them and compares them with the recorded output. The numbers make each protection visible in a few lines. They are not advice.
All limits apply inside one PHP process. Under PHP-FPM, a pipeline built inside a request starts every request fresh. See Limits.
Which problem, which policy
| Problem | Policy | Builder call | Guide |
|---|---|---|---|
| One slow call holds the caller for too long | Total deadline, plus an attempt timeout | newContext(timeout) and attemptTimeout(), both required | Concepts |
| Brief failures that a second try may fix | Retry with backoff, paid from a retry budget | retry(), retryBudget() | Retry and budget |
| A provider quota must not be exceeded | Rate limit | rateLimit() | Rate limiting |
| A dependency is down and callers keep hitting it | Circuit breaker | circuitBreaker() | Circuit breaker |
| One slow dependency uses up all concurrent work in the process | Bulkhead | bulkhead() | Bulkhead and cooldown |
| The server said "retry after N seconds" | Retry-After as a lower bound, plus a shared cooldown | ExceptionClassifier::retryAfterFrom(), cooldown() | Retry and budget, Bulkhead and cooldown |
| Callers need something useful when the call fails | Fallback | fallbackScope() and a fallback in newContext() | Fallback |
You pay only for what you configure. A policy exists only if you call its method.
The classifier decides what counts
Policies act on the outcome of each attempt, and your classifier decides what an outcome means. ExceptionClassifier starts conservative:
- A returned value is a success.
- An exception is not retried and not replaced by a fallback unless you list its class with
retryOn()orfallbackOn(). - Every exception counts as a circuit failure. You can list a class in
ignoreForCircuit(), for example the caller's own validation error, or narrow the failures withcountAsCircuitFailure().
unacceptableValuesWhen() marks a returned value as a failure. Such a value is a circuit failure, is never retried and is never replaced. retryAfterFrom() extracts a wait from an exception. The decision table is in the docblock of ExceptionClassifier (opens in a new tab).
Rate-limit, circuit, bulkhead and cooldown refusals, cancellations and storage errors are neutral. They never count as dependency failures.
Reqtide does not retry an exception nobody listed, because repeating an unexpected failure is a decision you must make explicitly. It still counts the exception as a circuit failure. A circuit that ignored unknown failures would stay closed against a dependency that fails in a way nobody anticipated. Matching uses instanceof, so a listed parent class or interface covers its subclasses.
Every snippet starts the same way
The protection guides build their pipelines from two required options. The attempt timeout is required. Cooperative means your code respects the time, because the synchronous runtime cannot stop it. The deadline you pass to newContext() is a separate, total budget for the whole operation.
/** Every pipeline below starts from the same two required options. */
$base = static fn(): PipelineBuilder => PipelineBuilder::profile('choosing', 'v1')
->partition('shop', 'prod', 'catalog-api')
->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative);
$ok = static fn(): string => 'ok';
$context = static fn(CompiledPipeline $pipeline): ExecutionContext => $pipeline->newContext(Duration::seconds(30));What is checked, in which order
For each attempt, Reqtide checks the deadline and the context first. Then it checks retry eligibility, the minimum remaining allowance and the retry budget reservation. The last two apply only to attempts after the first. Next come the circuit, the bulkhead, the shared cooldown and the rate limit, and then the dispatch. If a later check refuses, Reqtide releases the earlier permits. Nothing is held during a retry delay. Concepts shows the order as a table.
Sizing checklist
Reqtide checks the combination when you compile, and its messages name the option. These are the constraints it enforces. They are not recommended values.
- Every lease must exceed the attempt timeout by more than 1 second. This covers the bulkhead lease, the circuit permit lease and the retry budget reservation lease.
retry()with more than one attempt needsretryBudget(). AretryBudget()withoutretry()is rejected.- The circuit minimum samples cannot exceed a count window. The probe success threshold cannot exceed the probe capacity.
fallbackScope(IncludeAdmissionRefusals)needs at least one admission policy.
Everything else is a decision about your dependency: limits, windows, delays and pool sizes. Measure it, write the values down, and test the pipeline under virtual time (Testing with the virtual clock).
One guide per protection
- Retry and budget: attempts, backoff, jitter, the retry budget and Retry-After.
- Rate limiting: token bucket, fixed window, sliding counter, and what a charge means.
- Circuit breaker: windows, thresholds and probes.
- Bulkhead and cooldown: concurrency slots, leases and the shared cooldown.
- Fallback: what a fallback may replace, and what it never does.
Was this page helpful?