PHPackages                             gohany/rtry - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. gohany/rtry

ActiveLibrary

gohany/rtry
===========

Common implementation for retrying operations

v1.0.2(2mo ago)054611MITPHPPHP &gt;=7.4CI passing

Since Oct 2Pushed 2mo agoCompare

[ Source](https://github.com/Gohany/Rtry)[ Packagist](https://packagist.org/packages/gohany/rtry)[ Docs](https://github.com/Gohany/Rtry)[ RSS](/packages/gohany-rtry/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (3)Dependencies (6)Versions (4)Used By (1)

Rtry — A pragmatic PHP retry &amp; backoff toolkit
==================================================

[](#rtry--a-pragmatic-php-retry--backoff-toolkit)

[![tests](https://github.com/Gohany/Rtry/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/Gohany/Rtry/actions/workflows/tests.yml)[![codecov](https://camo.githubusercontent.com/2ad000d0dc00524e3602cbf4a6da87a026d28a8d222f7745218a19fbd6bc6753/68747470733a2f2f636f6465636f762e696f2f67682f476f68616e792f527472792f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/Gohany/Rtry)

Robust, composable retries for HTTP, RPC, queues, and DB work. Rtry focuses on **clear policy specs**, **safe defaults**, and **first‑class support** for jitter, hedging, deadlines, and rate‑limit headers.

> Namespaces used in this repo include `Gohany\Rtry` (implementation &amp; policy) and `Gohany\Retry` (interfaces / runtime). The library targets PHP **7.4+** and works on PHP 8.x.

---

Table of contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Install](#install)
- [Quick start](#quick-start)
- [Core concepts](#core-concepts)
    - [Retry runtime](#retry-runtime)
    - [Retry policy](#retry-policy)
    - [Delays &amp; sequences](#delays--sequences)
    - [Jitter](#jitter)
    - [Hedging](#hedging)
    - [Deadlines vs. per‑attempt timeout](#deadlines-vs-perattempt-timeout)
    - [Following rate‑limit headers](#following-rate-limit-headers)
    - [Deciders](#deciders)
    - [Rule‑based failure classification](#rule-based-failure-classification)
- [Array spec: authoring policies concisely](#array-spec-authoring-policies-concisely)
- [Hooks](#hooks)
- [Duration tokens](#duration-tokens)
- [Testing &amp; code coverage](#testing--code-coverage)
- [CI badges](#ci-badges)
- [Examples](#examples)
- [Contributing](#contributing)
- [License](#license)

The specification docs are in [SPEC.md](SPEC.md).
-------------------------------------------------

[](#the-specification-docs-are-in-specmd)

---

Features
--------

[](#features)

- **Simple API**: `Retry::try(callable $op, RetryPolicyInterface $policy)`
- **Expressive policies**: compact array spec (`a`, `sa`, `dl`, `cap`, `j`, `h`, `seq`, `fh`) or factory builders
- **Backoff modes** with **jitter** (full / plus‑minus / none)
- **Hedging** (concurrent lanes with stagger &amp; cancel policy)
- **Global deadlines** and **per‑attempt timeouts**
- **Follow server hints**: `Retry-After`, `RateLimit-Reset(-After)`, etc.
- **Deciders &amp; rules** to control retry on specific failures / tags / status families
- **PSR friendly**: `Psr\Clock\ClockInterface`, `Psr\Log\LoggerInterface`, `Psr\Http\Message\ResponseInterface`
- **Thorough tests** and examples

---

Install
-------

[](#install)

Via Composer:

```
composer require Gohany/Rtry
```

---

Quick start
-----------

[](#quick-start)

```
use Gohany\Rtry\Impl\Retry;
use Gohany\Rtry\Impl\RtryPolicyFactory;

// Build a policy from a string spec:
$factory = new RtryPolicyFactory();
$policy  = $factory->fromSpec('rtry:a=5;d=200;mode=exp;b=2;cap=8s;j=100@full;on=5xx,ETIMEDOUT');

$retry = new Retry(); // uses default Clock and Sleeper; no logger by default
$result = $retry->try(function () {
    // Your flaky operation (HTTP call, DB query, etc)
    return 'ok';
}, $policy);
```

---

Core concepts
-------------

[](#core-concepts)

### Retry runtime

[](#retry-runtime)

`Gohany\Rtry\Impl\Retry` orchestrates attempts:

- asks the policy for **startAfter**, **nextDelayMs(attempt)**, **attemptTimeoutMs**, **deadlineBudgetMs**, **capMs**, **backoffMode**
- applies **jitter** and **cap**
- optionally **follows headers** using the failure classifier (see below)
- respects **deadlines** and **per‑attempt timeouts**
- emits hooks: **betweenAttempts**, **onGiveUp**

### Retry policy

[](#retry-policy)

Implemented by `RetryPolicyInterface` (base) and enriched by `RtryPolicyInterface` (jitter/hedge/backoff/classifier). Build via `RtryPolicyFactory` from an array spec or use the underlying parts (e.g., `Attempts`, `Delay`, `Sequence`, `Jitter`, `Hedge`).

### Delays &amp; sequences

[](#delays--sequences)

Use a fixed backoff, exponential, or an explicit **sequence**.

```
use Gohany\Rtry\Impl\Parts\Sequence;

// Accepts multiple forms:
Sequence::make('50,100,1.5s*');
Sequence::make('(50,100ms,1.5s*)');
Sequence::make('seq=(50,100,1.5s,*)');

// Canonical string form:
(string) Sequence::make('50,100,1.5s*'); // "seq=(50,100,1.5s*)"

// '*' means repeat the last delay indefinitely.
```

### Jitter

[](#jitter)

Jitter smooths out stampedes by randomizing delays.

`JitterSpecInterface`:

- `mode()` — `'full' | 'pm' | 'none'` (pm = plus/minus)
- `percent()` — 0..1 (for plus/minus), or `null`
- `windowMs()` — absolute window (for plus/minus), or `null`
- `apply(int $nominalDelayMs, ?int $seed = null): int`

**Spec examples** for `'j'`:

```
'50@full', '100ms@full', '10m@full', '20%@full',
'50@pm',   '100ms@pm',   '10m@pm',   '20%@pm',

```

### Hedging

[](#hedging)

Run multiple lanes concurrently with a **stagger delay**, cancel others on first success/completion.

`HedgeSpecInterface`:

- `getLanes(): int`
- `getStaggerDelayMs(): int`
- `getCancelPolicy(): int` (`CANCEL_ON_FIRST_SUCCESS=0` or `CANCEL_ON_FIRST_COMPLETION=1`)

**Spec examples** for `'h'`:

```
'2@100ms', '2@10s', '2@1m', '2@1h',
'3@100&1'  // lanes=3, stagger=100ms, cancel-on-first-completion

```

### Deadlines vs per‑attempt timeout

[](#deadlines-vs-perattempt-timeout)

- **Global deadline** (`dl`) sets a total budget for the whole retry run (sleep time counts).
- **Per‑attempt timeout** (`timeout`) limits a single attempt duration.

If there’s not enough budget for the next delay, `Retry` **gives up** and triggers the `onGiveUp` hook.

### Following rate limit headers

[](#following-rate-limit-headers)

With `fh=1` (followHeaders=true), `Retry` consults the failure classifier to extract hints from responses:

- `Retry-After: 1` → minimum next delay = **1000ms**
- `Retry-After: Wed, 21 Oct 2015 07:28:00 GMT` → **notBeforeUnixMs**
- `RateLimit-Reset(-After)`, `X-RateLimit-Reset(-After)`, `X-RateLimit-Reset-MS` are recognized

The `RateLimitBackoffRule` returns `FailureMetadata` with `minNextDelayMs` or `notBeforeUnixMs`, and tags like `RATE_LIMITED`.

### Deciders

[](#deciders)

Control *whether* to retry:

- `AlwaysRetryDecider`
- `OnTokensDecider` — retry on status codes (`429`, `503`), families (`4xx`, `5xx`), or named tags (`RETRY-AFTER`, `TRANSIENT`)
- `CompositeDecider` — OR composition (short‑circuit)

### Rule‑based failure classification

[](#rulebased-failure-classification)

`RuleBasedFailureClassifier` applies **rules** to a `Throwable` to derive **status**, **tags**, **context patch**, **headers**, and **backoff hints**:

- `InstanceOfRule` — match on exception class
- `MessageRegexRule` — match on message
- `MethodStatusRule` — pull status from `getResponse(): ResponseInterface` or `getCode()`
- `RateLimitBackoffRule` — parse standard rate‑limit headers

Derived tags include `ETIMEDOUT`, `ECONNRESET`, `NETWORK_ERROR`, and DB `DEADLOCK` heuristics.

---

Rtry spec: authoring policies concisely
---------------------------------------

[](#rtry-spec-authoring-policies-concisely)

You can configure multiple candidate values per key (the factory will validate &amp; choose). Common keys:

KeyMeaningExamples`a`attempts`5`, `[5,10]``sa`start after`200`, `'200ms'`, `'200s'`, `'2m'`, `'1h'``dl`deadline budget`200`, `'200ms'`, `'2s'`, `'1m'``cap`cap for attempts`'200ms'`, `'2m'`, `'1h'``j`jitter`'20%@pm'`, `'100ms@pm'`, `'50@full'`, `'1.5m@full'``h`hedge`'2@100ms'`, `'3@100&1'``seq`explicit delay sequence`'(50,100ms,1.5s*)'`, `'seq=(50,100,1.5s,*)'`, `'50,100,1.5s*'` (canonical prints as `seq=…`)`fh`follow headers (bool)`true` / `false``b`exponential base`2`, `2.5``on`on tags`5xx`, `429`, `ratelimit`, `throttle`, `4xx`> **Tip:** numbers without unit default to **milliseconds**. `1.5m` and `1.5s` are supported; decimals for `h/m/s` are allowed.

---

Hooks
-----

[](#hooks)

```
$retry->setBetweenAttemptsHook(function (
    \Gohany\Retry\AttemptContextInterface $ctx,
    \Gohany\Retry\AttemptOutcomeInterface $outcome,
    \Gohany\Retry\RetryPolicyInterface $policy,
    int $sleepMs,
    array $lastHeaders
) {
    // observe sleep decision, metrics, logging, etc.
});

$retry->setOnGiveUpHook(function ($ctx, $outcome, $policy, $headers) {
    // after the final failure before giving up
});
```

---

Duration tokens
---------------

[](#duration-tokens)

- Accepted suffixes: `ms`, `s`, `m`, `h`
- Bare numbers = `ms`
- Decimals supported for `s/m/h` (e.g., `1.5s`, `2.5m`)
- Canonical formatting prefers compact, human‑readable units; sub‑second prints as bare `ms`

---

Testing &amp; code coverage
---------------------------

[](#testing--code-coverage)

Run the test suite:

```
composer install
composer test
```

**Note:** The test suite is configured to generate code coverage reports. If you encounter "No code coverage driver available", you'll need to install either Xdebug or PCOV:

### Installing a code coverage driver

[](#installing-a-code-coverage-driver)

**Option 1: PCOV (recommended for performance)**

```
pecl install pcov
```

Then add to your `php.ini`:

```
extension=pcov.so
```

**Option 2: Xdebug**

```
pecl install xdebug
```

Then add to your `php.ini`:

```
zend_extension=xdebug.so
xdebug.mode=coverage
```

**Run tests without coverage:**

```
composer test -- --no-coverage
```

---

Examples
--------

[](#examples)

### Retry with sequence + jitter + follow headers

[](#retry-with-sequence--jitter--follow-headers)

```
$spec = 'rtry:a5;seq=50,100,200,400,800*;j=20%@pm';
$policy = (new RtryPolicyFactory())->fromSpec($spec);
$result = (new Retry())->try(fn() => $client->get($url), $policy);
```

### Hedged requests

[](#hedged-requests)

```
$policy = (new RtryPolicyFactory())->fromSpec('rtry:a=1;h=3@75');
$retry = new Retry();
$resp  = $retry->try(fn() => $client->get($url), $policy);
```

### Deciders: retry on 4xx/5xx or named tags

[](#deciders-retry-on-4xx5xx-or-named-tags)

```
use Gohany\Rtry\Impl\Deciders\CompositeDecider;
use Gohany\Rtry\Impl\Deciders\OnTokensDecider;

$spec = 'rtry:a5;seq=50,100*;j=20%@pm;on=4xx,5xx,NETWORK_ERROR,RATE_LIMITED';
$policy = (new RtryPolicyFactory())->fromSpec($spec);
$retry = new Retry();
$resp  = $retry->try(fn() => $client->get($url), $policy);
```

---

Contributing
------------

[](#contributing)

- Run `composer test` (or `vendor/bin/phpunit`) before PRs
- Keep new rules/deciders covered with AAA‑style tests
- When adding new duration tokens or headers, document them here and in tests

---

License
-------

[](#license)

MIT (see `LICENSE`).

###  Health Score

39

—

LowBetter than 86% of packages

Maintenance82

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 50% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~70 days

Total

3

Last Release

89d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/ed29801d748ed62a9c9af22d1864c91911c9357003f1b4bc1bcfd8361ff46ecd?d=identicon)[Gohany](/maintainers/Gohany)

---

Top Contributors

[![Gohany](https://avatars.githubusercontent.com/u/10437234?v=4)](https://github.com/Gohany "Gohany (1 commits)")[![oranges13](https://avatars.githubusercontent.com/u/755110?v=4)](https://github.com/oranges13 "oranges13 (1 commits)")

---

Tags

Rtry

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/gohany-rtry/health.svg)

```
[![Health](https://phpackages.com/badges/gohany-rtry/health.svg)](https://phpackages.com/packages/gohany-rtry)
```

###  Alternatives

[symfony/symfony

The Symfony PHP framework

31.3k86.3M2.2k](/packages/symfony-symfony)[cakephp/cakephp

The CakePHP framework

8.8k18.5M1.6k](/packages/cakephp-cakephp)[simplesamlphp/saml2

SAML2 PHP library from SimpleSAMLphp

30317.2M40](/packages/simplesamlphp-saml2)[algolia/algoliasearch-client-php

API powering the features of Algolia.

69433.0M114](/packages/algolia-algoliasearch-client-php)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[tempest/framework

The PHP framework that gets out of your way.

2.1k23.1k9](/packages/tempest-framework)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
