OxPHP\Shared\* Naming Conventions
The OxPHP\Shared\* namespace is the application-level concurrency API:
Atomic, Counter, Flag, Map, Channel, Mutex, Once, Pool.
Method names follow a single set of rules so users can predict the API
without consulting the docs for every type.
This document is the canonical reference. New primitives, and changes to existing ones, MUST follow it.
Rules
1. Read a value — get()
PHP convention. Used by Map::get(), Counter::get(), Once::get().
Atomic::load(Ordering $order = Ordering::SeqCst) is the deliberate exception:
its existence carries the ordering argument, signalling that the read
is part of a memory-model contract distinct from a plain getter.
2. Write a value — set(), store() for atomics
Map::set(), Mutex value update (by reference inside withLock), Once::getOrInit().
Atomic::store(int $value, Ordering $order = Ordering::SeqCst) mirrors load for the same reason.
3. Number of elements — count(): int
Every container that exposes its current size does so under the name
count(): int. Channel additionally implements \Countable, so
count($ch) works as a native idiom for queued items. Map exposes
count(): int as a method but does not implement \Countable, so
call it directly. Pool has no count(); read its live slots from
stats()->size():
$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(), and pending() are forbidden on the public
surface, regardless of which language the implementer's muscle
memory comes from.
4. Boolean-getter — is*() prefix
Channel::isClosed().
No bare verbs (test, check) and no domain-specific names
(closed). The is prefix marks a pure read of a boolean property.
A type whose state is richer than a single boolean exposes it as a
status() method returning an enum instead of an is*() getter.
Channel's RecvResult::status() and Once::status(): Once\Status
(Uninitialized/Pending/Ready/Poisoned) follow this. Reach for status()
when the answer has more than two cases.
Mutex does not expose isCorrupted(). Corruption is sticky,
non-recoverable, and surfaced via CorruptedMutexException on the
next acquire. There's nothing useful to do with the probe other than
re-acquire and catch.
5. Wait-policy trichotomy — try* / bare / *Timeout
Blocking primitives (Channel, Mutex) express the wait policy
through the method name, not through an overloaded ?float $timeout
argument:
| Suffix | Behaviour | Examples |
|---|---|---|
try* |
Non-blocking; reports the failure variant immediately. | Channel::trySend, Channel::tryRecv, Mutex::tryWithLock |
| (bare name) | Block forever (or until the request fiber is cancelled). | Channel::send, Channel::recv, Mutex::withLock |
*Timeout |
Bounded wait. Takes a mandatory int $ms > 0. |
Channel::sendTimeout, Channel::recvTimeout, Mutex::withLockTimeout |
The trichotomy moves three ambiguous policies (null = forever, 0
= try, positive = bounded) out of one parameter and into three
methods with self-documenting names.
The $ms argument on *Timeout methods is strictly positive. Zero,
negative, non-int, and absent values raise OxPHP\Shared\TypeException
at the bridge.
Conditional-success ops live on Map under the setIfAbsent
spelling rather than try*: Map::setIfAbsent commits only when the
key was absent (parallel to HashMap::try_insert) and returns the
existing value, or null when the insert happened.
The setIfAbsent name is reserved for that single semantics; do not
reuse it elsewhere.
How a try* method reports failure depends on the type: Channel
returns a value-typed Result, Mutex throws a ContentionException,
Pool::tryAcquire() returns null when the pool is saturated, and
Once::trySet() returns false when the cell is already Ready or
Pending.
6. Compare-and-swap — compareAndSet()
Atomic::compareAndSet(), Flag::compareAndSet(). Always returns
bool (the swap happened, or it didn't).
7. Replace and return previous — swap()
Atomic::swap() for ints, Flag::swap() for bools. Returns the
previous value.
8. Atomic RMW returning previous — fetch*() prefix
Atomic::fetchAdd(), fetchSub(), fetchAnd(), fetchOr(),
fetchXor().
The fetch prefix encodes the return contract: the value before
the operation. This contrasts with Counter::add(), which returns the new
value (LongAdder-style aggregate counter).
When adding new RMW methods, pick the contract first, then the name:
- prev-value return →
fetchVerb(args) - new-value return → bare
verb(args)
Do not mix.
9. Reset to default — clear()
Map::clear() empties the container and returns the number of
entries removed.
Counter has no clear(); set(0) is its windowed reset.
Counter::set() is the documented exception that returns the
previous value (not void): it is the atomic exchange, and
set(0) reading the prior total is the LongAdder sumThenReset
idiom. (Atomic spells the same operation swap(); Counter keeps
set because set($n) reads naturally for seeding and windowing.)
10. Registry identity — id(): int
Every Shared\* instance exposes id(): int for logs and the
/__ox_shared/entry?id=<id> observability endpoint.
Cheat sheet
| Concept | Canonical name | Examples |
|---|---|---|
| Read a value | get() |
Map::get, Counter::get |
| Read an atomic | load($order) |
Atomic::load |
| Write a value | set() |
Map::set |
| Write an atomic | store($v, $order) |
Atomic::store |
| Number of elements | count(): int |
Map::count, Channel::count |
| Boolean property | is*(): bool |
Channel::isClosed |
| Conditional insert | setIfAbsent($k, $v) |
Map::setIfAbsent |
| Non-blocking wait | try*() |
Channel::trySend, Mutex::tryWithLock |
| Forever wait | bare verb | Channel::send, Channel::recv, Mutex::withLock |
| Bounded wait | *Timeout(int $ms) |
Channel::sendTimeout, Mutex::withLockTimeout |
| Compare-and-swap | compareAndSet() |
Atomic::compareAndSet |
| Swap, return prev | swap() |
Atomic::swap, Flag::swap |
| Atomic RMW, return prev | fetch*() |
Atomic::fetchAdd |
| Atomic RMW, return new | bare verb | Counter::add |
| Reset to default | clear() |
Map::clear |
| Registry id | id(): int |
every Shared\* type |
Adding a new Shared\* type
When proposing a new primitive, fill out this checklist before merging:
- Every method maps to a row in the cheat sheet, or has an ADR
explaining the exception (see
Atomic::load/storeandCounter::setabove). - If the type holds a collection of values, it implements
\Countableand exposescount(): int. - Read methods are
getorload(atomic only). - Boolean getters use the
is*prefix. - Wait-policy variants follow the
try*/ bare /*Timeout(int $ms)trichotomy. The*Timeoutvariant takesint $ms > 0and rejects zero / negative / non-int input withTypeException. Wait-policytry*methods return either a value-typed Result or throw a domain exception, and never usenullto encode failure.Pool::tryAcquire(), which returnsnullwhen the pool is saturated, does not follow this. Conditional-success ops follow the dedicatedsetIfAbsentnaming instead oftry*. - No
len,size,pending,test, or other ad-hoc names. - Domain-specific verbs (
evict,drain,flush, etc.) appear only when no canonical entry in the cheat sheet covers the concept.
Observability names
The operator-facing surface (Prometheus metric names and the JSON at
/__ox_shared/entry?id=<id>) is a separate contract from the PHP API,
because renaming it breaks dashboards and alert rules. The element
count of a Channel or a Pool is oxphp_shared_channel_count or
oxphp_shared_pool_count on /metrics, and count in its
type_specific JSON. For a Channel that is the number
Channel::count() returns; for a Pool it is the live slots, the number
Pool::stats()->size() returns.
The earlier spellings (oxphp_shared_channel_pending,
oxphp_shared_pool_size, and the JSON keys pending and size) are
no longer emitted. Dashboards and alert rules still written against
them find no series, so a threshold alert on them never fires.
Stability
These rules are part of the OxPHP\Shared\* 1.0 contract. After the
1.0 release, renames are breaking changes and require a deprecation
cycle. Before 1.0, the rules are still binding, and new methods that
violate them will be rejected in review.