Соглашения об именовании 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 = Ordering::SeqCst) — намеренное исключение: его наличие несёт аргумент упорядочивания, сигнализируя, что чтение является частью контракта модели памяти и отличается от обычного геттера.

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

Map::set(), обновление значения Mutex (по ссылке внутри withLock), Once::getOrInit(). Atomic::store(int $value, Ordering $order = Ordering::SeqCst) повторяет load по той же причине.

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

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

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->stats()->size(); // 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 фиксирует изменение только когда ключ отсутствовал (параллель к HashMap::try_insert), и возвращает существующее значение или null, если вставка произошла. Имя setIfAbsent зарезервировано за этой единственной семантикой; не переиспользуйте его в других местах.

То, как метод try* сообщает о неудаче, зависит от типа: Channel возвращает типизированный по значению Result, Mutex бросает ContentionException, Pool::tryAcquire() возвращает null, когда пул насыщен, а Once::trySet() возвращает false, когда ячейка уже в состоянии Ready или Pending.

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() — опустошает контейнер; возвращает число удалённых записей.

У 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
Булево свойство 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. Pool::tryAcquire(), который возвращает null, когда пул насыщен, этому правилу не следует. Операции с условным успехом следуют выделенному именованию setIfAbsent вместо try*.
  • Никаких len, size, pending, test или других произвольных имён.
  • Предметно-специфичные глаголы (evict, drain, flush и т. д.) появляются только тогда, когда ни одна каноническая запись в шпаргалке не покрывает концепцию.

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

Интерфейс для оператора — имена метрик Prometheus и JSON по адресу /__ox_shared/entry?id=<id> — это отдельный от PHP API контракт, потому что его переименование ломает дашборды и правила алертов. Число элементов Channel или Pool — это oxphp_shared_channel_count или oxphp_shared_pool_count на /metrics и count в его JSON type_specific. Для Channel это число, которое возвращает Channel::count(); для Pool — живые слоты, то есть число, которое возвращает Pool::stats()->size().

Удалено в 0.13.0

Прежние написания — oxphp_shared_channel_pending, oxphp_shared_pool_size и JSON-ключи pending и size — больше не выдаются. Дашборды и правила алертов, всё ещё написанные под них, не находят ни одной серии, поэтому пороговый алерт по ним никогда не срабатывает.

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

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

Нашли ошибку? Сообщите →