Shared\Counter

OxPHP\Shared\Counter — це атомарне 64-бітне ціле число зі знаком, спільне для всього процесу й призначене для накопичення: підрахунку подій, підсумовування приростів, ковзних підсумків за вікно. Усі операції неблокувальні: два воркери, що додають одночасно, ніколи не втратять жодного інкременту.

Для довільного атомарного стану, який має синхронізувати іншу пам’ять (скінченні автомати, мітки версій, seqlock-и, маски бітових прапорців), використовуйте натомість Shared\Atomic.

Огляд

  • Атомарний int64. Діапазон −9_223_372_036_854_775_808 … 9_223_372_036_854_775_807. Переповнення циклічне.
  • Неблокувальний. add компілюється в одну інструкцію fetch_add.
  • Завжди Relaxed. Операції атомарні (жодних втрачених інкрементів, жодних розірваних читань), але не встановлюють відношення happens-before з іншою пам’яттю. Counter — це статистика, а не точка синхронізації: якщо вам потрібен порядок, використовуйте Shared\Atomic.
  • Shareable. Екземпляри можна зберігати всередині Shared\Map / Shared\Channel і передавати у файбери через захоплення use.

Довідник API

php
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; }
Метод Повертає Призначення
get поточне Читання без зміни.
set попереднє Атомарний обмін; set(0) — читання з обнуленням наприкінці вікна.
add нове add() збільшує на 1, add(-1) зменшує, інакше — будь-який приріст.
compareAndSet bool Обмежені / насичувальні лічильники (стеля, підлога) через цикл CAS.
id id у реєстрі Логування, трасування, зіставлення з /__ox_shared/entry?id=….

Приклади

Лічильник запитів воркера

php
<?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"; });

Перехід до нового вікна

php
<?php $hits = new OxPHP\Shared\Counter(); // Every N minutes in your cron/worker loop: $prev = $hits->set(0); // atomically reads and zeroes logWindowMetric($prev);

Обмежений лічильник (цикл CAS)

php
<?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));

Пакетне накопичення

php
<?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));

Семантика та підводні камені

set() повертає попереднє значення, а потім записує нове — атомарно. set(0) — це патерн «знімок і обнулення» (LongAdder::sumThenReset); set($n) задає будь-яку нову початкову точку.

Порядок Relaxed

Кожна операція атомарна, але Counter не публікує іншу пам’ять. Якщо читач має побачити дані, які записувач записав до збільшення числа, — це вже синхронізація: використовуйте Shared\Atomic з Ordering::Release/Acquire.

compareAndSet працює з порядком Relaxed/Relaxed і не приймає аргументів порядку. Він коректний для рішень, що ухвалюються на основі власного значення лічильника (стеля, підлога, захоплення за значенням). CAS, що публікує інший стан, — це справа Shared\Atomic.

Переповнення циклічне

Додавання понад INT_MAX переходить до INT_MIN. Для лічильників, що працюють місяцями з тисячами операцій на секунду, тримайте значення в межах десятків трильйонів або періодично скидайте його.

Жодних дробових значень. Рахуєте байти, щоб отримати середні з точністю float? Відстежуйте чисельник (Counter) і знаменник (Counter) окремо й діліть під час читання.

Винятки

Виняток Коли виникає
StaleHandleException Будь-який метод дескриптора, чий запис у реєстрі витіснено.
UninitializedException id() в обгортці, яка ще не завершила __construct.

Лічильники ніколи не кидають винятків через переповнення чи екстремальні значення — вони циклічно переходять.

Спостережуваність

Повний огляд — у розділі Спостережуваність спільного стану. Коротка довідка:

  • GET /__ox_shared/entry?id=N показує { value, type: "Counter" }.
  • Gauge Prometheus oxphp_shared_counter_value{counter_id="…"} відстежує поточне значення.
  • Лічильники всього реєстру (oxphp_shared_operations_total, oxphp_shared_objects_total) охоплюють Counter через мітку type="Counter".

Коли не використовувати

  • Числа з рухомою комою чи десяткові дроби. Використовуйте пару Counter (чисельник / знаменник) або Shared\Mutex<array{total_cents: int, count: int}>.
  • Нечислові події, яким потрібен багатий контекст. Якщо вам потрібні {count, last_actor, last_reason}, пов’язані з одним ключем, беріть Shared\Map або Shared\Mutex.
  • Підсумки між хостами. Counter існує лише в межах процесу. Для агрегації з кількох хостів використовуйте конвеєр метрик (Prometheus + rate() або центральний Redis INCR).
  • Довговічність. Стан Counter зникає під час зупинки сервера. Якщо підсумок має пережити перезапуск, зберігайте знімки деінде.

Промпти для вашого агента

Ці промпти розраховані на агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.

prompt
Використай OxPHP Shared Counter у моєму коді замість інкременту APCu чи статичної властивості, якими я зараз підраховую запити, звернення або використання за орендарями. Прочитай сторінку `shared-state/shared-counter` через MCP-сервер документації OxPHP. Знайди, де в цьому репозиторії так рахують, і покажи мені заміну на Counter, зокрема прив’язку `Registry::counter`, яка зберігає один спільний підсумок для всіх воркерів.

Пов’язані сторінки

  • Спільний стан — огляд і патерни міграції.
  • Shared\Atomic — універсальний атомарний int64 із CAS, swap і повним керуванням порядком пам’яті.
  • Shared\Map — коли лічильники прив’язані до ключів (Map<string, Counter>).
  • Shared\Flag — коли значення лише «увімкнено/вимкнено».
  • Shared\Mutex — коли лічильник має оновлюватися синхронно з іншими полями.