Shared\Counter
OxPHP\Shared\Counter est un entier signé atomique de 64 bits à l'échelle du processus, spécialisé dans l'accumulation : compter des événements, additionner des deltas, tenir des totaux sur fenêtre glissante. Chaque opération est sans verrou ; deux workers qui incrémentent simultanément ne perdent jamais un tick.
Pour un état atomique arbitraire qui doit synchroniser d'autres zones mémoire (machines à états, estampilles de version, seqlocks, masques de bitflags), utilisez plutôt Shared\Atomic.
Vue d'ensemble
- int64 atomique. Plage
−9_223_372_036_854_775_808 … 9_223_372_036_854_775_807. Le dépassement boucle. - Sans verrou.
addse compile en un uniquefetch_add. - Toujours Relaxed. Les opérations sont atomiques (aucun tick perdu, aucune lecture déchirée) mais n'établissent aucun happens-before avec les autres zones mémoire. Un Counter est une statistique, pas un point de synchronisation — si vous avez besoin d'ordonnancement, utilisez
Shared\Atomic. - Partageable. Les instances peuvent être stockées dans
Shared\Map/Shared\Channelet transmises aux Fibers via des capturesuse.
Référence de l'API
namespace OxPHP\Shared;
final class Counter implements Shareable
{
public function __construct(int $initial = 0);
public function get(): int; // current
public function set(int $value): int; // returns previous; set(0) = window reset
public function add(int $delta = 1): int; // returns new; add()=+1, add(-1)=decrement
public function compareAndSet(int $expect, int $new): bool;
public function id(): int;
}| Méthode | Retourne | Cas d'usage |
|---|---|---|
get |
valeur courante | Lecture sans mutation. |
set |
valeur précédente | Échange atomique ; set(0) est la lecture-et-remise-à-zéro de fin de fenêtre. |
add |
nouvelle valeur | add() incrémente de 1, add(-1) décrémente, tout autre delta sinon. |
compareAndSet |
bool | Compteurs bornés / saturants (plafond, plancher) via une boucle CAS. |
id |
id de registre | Journalisation, traçage, corrélation /__ox_shared/entry?id=…. |
Exemples
Compteur de requêtes par worker
<?php
$requests = new OxPHP\Shared\Counter();
oxphp_worker(function () use ($requests) {
$count = $requests->add(); // +1, returns the new total
header("X-Request-Count: {$count}");
echo "ok";
});Bascule par fenêtre
<?php
$hits = new OxPHP\Shared\Counter();
// Every N minutes in your cron/worker loop:
$prev = $hits->set(0); // atomically reads and zeroes
logWindowMetric($prev);Compteur borné (boucle CAS)
<?php
$slots = new OxPHP\Shared\Counter();
$cap = 100;
// Claim a slot only while under the cap.
do {
$cur = $slots->get();
if ($cur >= $cap) {
// full — reject
break;
}
} while (!$slots->compareAndSet($cur, $cur + 1));Accumulation groupée
<?php
$bytes = new OxPHP\Shared\Counter();
// Sum a batch in PHP, then one atomic add (one FFI call).
$deltas = array_map(fn ($req) => strlen($req['body']), $batch);
$newTotal = $bytes->add(array_sum($deltas));Sémantique et pièges
set() retourne la valeur précédente, puis stocke — atomiquement. set(0) est le motif capture-et-remise-à-zéro (LongAdder::sumThenReset) ; set($n) amorce un nouveau point de départ.
Chaque opération est atomique, mais un Counter ne publie pas d'autres zones mémoire. Si un lecteur doit observer des données qu'un écrivain a écrites avant d'incrémenter l'entier, cela relève de la synchronisation — utilisez Shared\Atomic avec Ordering::Release/Acquire.
compareAndSet est Relaxed/Relaxed et ne prend aucun argument d'ordonnancement. Il est correct pour des décisions prises sur la valeur propre du compteur (plafond, plancher, réservation par valeur). Un CAS qui publie un autre état relève de Shared\Atomic.
Additionner au-delà de INT_MAX boucle vers INT_MIN. Pour des compteurs qui tournent pendant des mois à plusieurs milliers par seconde, gardez la valeur dans la plage des dizaines de milliers de milliards ou remettez-la à zéro périodiquement.
Pas de valeurs fractionnaires. Vous comptez des octets pour des moyennes à précision flottante ? Suivez le numérateur (Counter) et le dénominateur (Counter) séparément et divisez au moment de la lecture.
Exceptions
| Exception | Levée par |
|---|---|
StaleHandleException |
Toute méthode sur un handle dont l'entrée de registre a été évincée. |
UninitializedException |
id() sur un wrapper qui n'a pas terminé son __construct. |
Les Counters ne lèvent jamais d'exception en cas de dépassement ou de valeurs extrêmes — ils bouclent.
Observabilité
Voir Observabilité de l'état partagé pour la visite complète. Références rapides :
GET /__ox_shared/entry?id=Nexpose{ value, type: "Counter" }.- La jauge Prometheus
oxphp_shared_counter_value{counter_id="…"}suit la valeur courante. - Les compteurs à l'échelle du registre (
oxphp_shared_operations_total,oxphp_shared_objects_total) couvrent Counter via le labeltype="Counter".
Quand ne pas l'utiliser
- Flottants ou décimaux. Utilisez une paire de Counters (numérateur / dénominateur) ou un
Shared\Mutex<array{total_cents: int, count: int}>. - Événements non numériques nécessitant un contexte riche. Si vous avez besoin de
{count, last_actor, last_reason}couplé à une seule clé, tournez-vous versShared\MapouShared\Mutex. - Totaux inter-hôtes. Un Counter est strictement in-process. Pour une agrégation multi-hôtes, utilisez un pipeline de métriques (Prometheus +
rate(), ou unINCRRedis centralisé). - Durabilité. L'état d'un Counter s'évapore à l'arrêt du serveur. Persistez des snapshots ailleurs si le total doit survivre aux redémarrages.
Voir aussi
- État partagé — vue d'ensemble et motifs de migration.
- Shared\Atomic — int64 atomique générique avec CAS, swap et contrôle complet de l'ordonnancement mémoire.
- Shared\Map — quand les compteurs sont indexés par clé (
Map<string, Counter>). - Shared\Flag — quand la valeur est simplement activée/désactivée.
- Shared\Mutex — quand un compteur doit être mis à jour en lockstep avec d'autres champs.