Соглашения об именовании OxPHP\Shared\*

Пространство имён OxPHP\Shared\* — это API конкурентности уровня приложения: Atomic, Counter, Flag, Map, Channel, Mutex, Once, Pool. Имена методов подчиняются единому набору правил, чтобы можно было предсказать API, не заглядывая в документацию по каждому типу.

Этот документ — канонический справочник. Новые примитивы и изменения существующих ОБЯЗАНЫ ему следовать.

Правила

1. Чтение значения — get()

Соглашение PHP. Используется в Map::get(), Counter::get(), Once::get().

Atomic::load(?Ordering $order = null) — намеренное исключение: его наличие несёт аргумент упорядочивания, сигнализируя, что чтение является частью контракта модели памяти и отличается от обычного геттера.

2. Запись значения — set(), store() для атомиков

Map::set(), сброс значения Mutex (через with), Once::getOrInit(). Atomic::store($value, ?Ordering) повторяет load по той же причине.

3. Число элементов — count(): int

Каждый контейнер, раскрывающий свой текущий размер, делает это под именем count(): int. Channel дополнительно реализует \Countable, поэтому count($ch) работает как нативная идиома для элементов в очереди. Map и Pool предоставляют count(): int как метод, но не реализуют \Countable — вызывайте его напрямую:

php
$ch = new OxPHP\Shared\Channel(1024); $map = new OxPHP\Shared\Map(); $pool = new OxPHP\Shared\Pool($factory); count($ch); // queued items (Channel implements \Countable) $map->count(); // entries $pool->count(); // total live slots (in-use + idle)

Никаких size(), len() или pending() — они запрещены в публичном интерфейсе, независимо от того, из какого языка идёт мышечная память разработчика.

4. Булев геттер — префикс is*()

Channel::isClosed().

Никаких голых глаголов (test, check) и никаких предметно-специфичных имён (closed). Префикс is обозначает чистое чтение булева свойства.

Тип, состояние которого богаче одного булева значения, раскрывает его через метод status(), возвращающий enum, а не через геттер is*() — так устроены RecvResult::status() у Channel и Once::status(): Once\Status (Uninitialized/Pending/Ready/Poisoned). Обращайтесь к status(), когда ответ имеет больше двух вариантов.

Mutex не предоставляет isCorrupted() — повреждение постоянно, невосстановимо и всплывает через CorruptedMutexException при следующем захвате. С такой проверкой нельзя сделать ничего полезного, кроме как повторно захватить и перехватить исключение.

5. Трихотомия политик ожидания — try* / голое имя / *Timeout

Блокирующие примитивы (Channel, Mutex) выражают политику ожидания через имя метода, а не через перегруженный аргумент ?float $timeout:

Суффикс Поведение Примеры
try* Неблокирующее; немедленно сообщает о варианте неудачи. Channel::trySend, Channel::tryRecv, Mutex::tryWithLock
(голое имя) Блокирует навсегда (или пока не будет отменён файбер запроса). Channel::send, Channel::recv, Mutex::withLock
*Timeout Ограниченное ожидание. Принимает обязательный int $ms > 0. Channel::sendTimeout, Channel::recvTimeout, Mutex::withLockTimeout

Трихотомия выносит три неоднозначные политики (null = навсегда, 0 = попытка, положительное = ограниченное) из одного параметра в три метода с самодокументируемыми именами.

Note

Аргумент $ms в методах *Timeout строго положителен. Ноль, отрицательные, не-int и отсутствующие значения вызывают OxPHP\Shared\TypeException на мосту.

Операции с условным успехом располагаются на Map под написанием setIfAbsent, а не try*: Map::setIfAbsent фиксирует изменение только когда ключ отсутствовал, и возвращает bool (параллель к HashMap::try_insert). Имя setIfAbsent зарезервировано за этой единственной семантикой; не переиспользуйте его в других местах.

Объединяющий инвариант для try*: он либо возвращает типизированный по значению Result (Channel), либо бросает ContentionException (Mutex). Он никогда не возвращает null, чтобы закодировать «не удалось». Так работал старый API, и это порождало неоднозначность с оператором null-объединения, которую устраняет трихотомия.

6. Сравнение с обменом (compare-and-swap) — compareAndSet()

Atomic::compareAndSet(), Flag::compareAndSet(). Всегда возвращает bool (обмен произошёл или нет).

7. Замена с возвратом предыдущего значения — swap()

Atomic::swap() для int, Flag::swap() для bool. Возвращает предыдущее значение.

8. Атомарный RMW с возвратом предыдущего — префикс fetch*()

Atomic::fetchAdd(), fetchSub(), fetchAnd(), fetchOr(), fetchXor().

Префикс fetch кодирует контракт возврата: значение до операции. Это контрастирует с Counter::add(), который возвращает новое значение (агрегирующий счётчик в стиле LongAdder).

При добавлении новых RMW-методов сначала выбирайте контракт, затем имя:

  • возврат предыдущего значения → fetchVerb(args)
  • возврат нового значения → голое verb(args)

Не смешивайте.

9. Сброс к значению по умолчанию — clear()

Map::clear() — опустошает контейнер; возвращает void.

У Counter нет clear()set(0) служит его оконным сбросом. Counter::set() — задокументированное исключение, которое возвращает предыдущее значение (а не void): это атомарный обмен, и set(0), читающий прежнюю сумму, — это идиома sumThenReset из LongAdder. (Atomic записывает ту же операцию как swap(); Counter сохраняет set, потому что set($n) естественно читается при задании начального значения и оконном сбросе.)

10. Идентичность в реестре — id(): int

Каждый экземпляр Shared\* предоставляет id(): int для логов и эндпоинта наблюдаемости /__ox_shared/entry?id=<id>.

Шпаргалка

Концепция Каноническое имя Примеры
Чтение значения get() Map::get, Counter::get
Чтение атомика load($order) Atomic::load
Запись значения set() Map::set
Запись атомика store($v, $order) Atomic::store
Число элементов count(): int Map::count, Channel::count, Pool::count
Булево свойство is*(): bool Channel::isClosed
Условная вставка setIfAbsent($k, $v) Map::setIfAbsent
Неблокирующее ожидание try*() Channel::trySend, Mutex::tryWithLock
Ожидание навсегда голый глагол Channel::send, Channel::recv, Mutex::withLock
Ограниченное ожидание *Timeout(int $ms) Channel::sendTimeout, Mutex::withLockTimeout
Сравнение с обменом compareAndSet() Atomic::compareAndSet
Обмен с возвратом предыдущего swap() Atomic::swap, Flag::swap
Атомарный RMW, возврат предыдущего fetch*() Atomic::fetchAdd
Атомарный RMW, возврат нового голый глагол Counter::add
Сброс к значению по умолчанию clear() Map::clear
Идентификатор в реестре id(): int каждый тип Shared\*

Добавление нового типа Shared\*

Предлагая новый примитив, заполните этот чек-лист перед слиянием:

  • Каждый метод соответствует строке в шпаргалке или имеет ADR, объясняющий исключение (см. Atomic::load/store и Counter::set выше).
  • Если тип хранит коллекцию значений, он реализует \Countable и предоставляет count(): int.
  • Методы чтения — get или load (только для атомиков).
  • Булевы геттеры используют префикс is*.
  • Варианты политики ожидания следуют трихотомии try* / голое имя / *Timeout(int $ms). Вариант *Timeout принимает int $ms > 0 и отвергает ноль / отрицательные / не-int значения с TypeException. Методы политики ожидания try* возвращают либо типизированный по значению Result, либо бросают доменное исключение — но никогда не кодируют результат через null. Операции с условным успехом следуют выделенному именованию setIfAbsent вместо try*.
  • Никаких len, size, pending, test или других произвольных имён.
  • Предметно-специфичные глаголы (evict, drain, flush и т. д.) появляются только тогда, когда ни одна каноническая запись в шпаргалке не покрывает концепцию.

Имена в наблюдаемости отстают от PHP API

Интерфейс для оператора — имена метрик Prometheus и JSON по адресу /__ox_shared/entry?id=<id> — это отдельный от PHP API контракт. Его переименование ломает дашборды и правила алертов. Чтобы избежать скрытого рассогласования, затронутые имена выдаются дважды в течение одного цикла релиза:

Интерфейс Устаревшее (всё ещё выдаётся) Каноническое
Prometheus oxphp_shared_channel_pending oxphp_shared_channel_count
Prometheus oxphp_shared_pool_size oxphp_shared_pool_count
Запись JSON Channel.pending Channel.count
Запись JSON Pool.size Pool.count

Строки # HELP устаревших метрик содержат префикс (deprecated, removed in a future release; use *_count), а плагин ox_shared выдаёт при старте WARN, когда включена интроспекция или метрики.

Миграция

Переведите дашборды и правила алертов на имена с _count до закрытия цикла устаревания. После удаления будут выдаваться только канонические имена, и панели Prometheus/Grafana, ссылающиеся на старые, начнут возвращать пустые серии.

Стабильность

Эти правила — часть контракта OxPHP\Shared\* 1.0. После релиза 1.0 переименования являются ломающими изменениями и требуют цикла устаревания. До 1.0 правила всё равно обязательны — новые методы, которые их нарушают, будут отклонены на ревью.