OxPHP\Shared\* 命名约定

OxPHP\Shared\* 命名空间是应用层的并发 API: AtomicCounterFlagMapChannelMutexOncePool。 方法命名遵循一套统一的规则,用户无需为每种类型都查阅文档就能预判 API。

本文档是规范参考。新增原语以及对现有原语的改动都必须遵循它。

规则

1. 读取值——get()

PHP 惯例。Map::get()Counter::get()Once::get() 都使用它。

Atomic::load(?Ordering $order = null) 是有意为之的例外: 它的存在携带了 ordering 参数,表明这次读取属于内存模型契约的一部分, 有别于普通的 getter。

2. 写入值——set(),原子类型用 store()

Map::set()Mutex 值重置(通过 with)、Once::getOrInit()Atomic::store($value, ?Ordering) 出于同样的原因与 load 对称。

3. 元素数量——count(): int

每个暴露当前大小的容器都以 count(): int 命名来实现。Channel 额外实现了 \Countable,因此 count($ch) 是获取队列中项数的原生 惯用写法。MapPoolcount(): 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. 布尔 getter——is*() 前缀

Channel::isClosed()

不用裸动词(testcheck),也不用领域特定的名称(closed)。 is 前缀标记的是对某个布尔属性的纯读取。

如果某类型的状态比单个布尔值更丰富,则应将其暴露为返回枚举的 status() 方法,而非 is*() getter——ChannelRecvResult::status()Once::status(): Once\Status (Uninitialized/Pending/Ready/Poisoned)都遵循这一点。当答案有超过 两种情况时,就应选用 status()

Mutex 暴露 isCorrupted()——损坏状态是黏性的、不可恢复的, 会在下次获取锁时通过 CorruptedMutexException 浮现出来。除了重新 获取锁并捕获异常之外,对这个探测结果没有任何有用的操作。

5. 等待策略三分法——try* / 裸名 / *Timeout

阻塞型原语(Channel、Mutex)通过方法名而非重载的 ?float $timeout 参数来表达等待策略

后缀 行为 示例
try* 非阻塞;立即报告失败变体。 Channel::trySendChannel::tryRecvMutex::tryWithLock
(裸名) 永久阻塞(或直到请求纤程被取消)。 Channel::sendChannel::recvMutex::withLock
*Timeout 有界等待。接受一个必填的 int $ms > 0 Channel::sendTimeoutChannel::recvTimeoutMutex::withLockTimeout

三分法把三种含义模糊的策略(null = 永久,0 = 尝试,正值 = 有界) 从一个参数里拆出来,放进三个名称自解释的方法中。

Note

*Timeout 方法上的 $ms 参数必须严格为正。零、负数、非 int 以及缺省值都会在桥接层抛出 OxPHP\Shared\TypeException

条件成功型操作放在 Map 上,采用 setIfAbsent 的写法而非 try*Map::setIfAbsent 仅在键不存在时提交,并返回 bool (与 HashMap::try_insert 对应)。setIfAbsent 这个名称专门保留 给这一种语义;不要在别处复用它。

try* 的统一不变式:它要么返回一个值类型的 Result(Channel), 要么抛出 ContentionException(Mutex)。它绝不会返回 null 来 编码「未成功」。那是旧 API 的做法,会产生 null 合并的歧义,而三分法 正是要消除这种歧义。

6. 比较并交换——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) 读取先前的总量正是 LongAdder 的 sumThenReset 惯用法。(Atomic 把同样的操作命名为 swap(); Counter 保留 set,因为 set($n) 用于播种和窗口化时读起来更自然。)

10. 注册表标识——id(): int

每个 Shared\* 实例都暴露 id(): int,供日志和 /__ox_shared/entry?id=<id> 可观测性端点使用。

速查表

概念 规范名称 示例
读取值 get() Map::getCounter::get
读取原子值 load($order) Atomic::load
写入值 set() Map::set
写入原子值 store($v, $order) Atomic::store
元素数量 count(): int Map::countChannel::countPool::count
布尔属性 is*(): bool Channel::isClosed
条件插入 setIfAbsent($k, $v) Map::setIfAbsent
非阻塞等待 try*() Channel::trySendMutex::tryWithLock
永久等待 裸动词 Channel::sendChannel::recvMutex::withLock
有界等待 *Timeout(int $ms) Channel::sendTimeoutMutex::withLockTimeout
比较并交换 compareAndSet() Atomic::compareAndSet
交换并返回旧值 swap() Atomic::swapFlag::swap
原子 RMW,返回旧值 fetch*() Atomic::fetchAdd
原子 RMW,返回新值 裸动词 Counter::add
重置为默认值 clear() Map::clear
注册表 id id(): int 每个 Shared\* 类型

新增 Shared\* 类型

在提议新原语时,合并前请先填写这份检查清单:

  • 每个方法都能对应到速查表中的一行,或有一份 ADR 解释其例外 (参见上文的 Atomic::load/storeCounter::set)。
  • 如果该类型持有值的集合,则它实现 \Countable 并暴露 count(): int
  • 读取方法为 getload(仅限原子类型)。
  • 布尔 getter 使用 is* 前缀。
  • 等待策略变体遵循 try* / 裸名 / *Timeout(int $ms) 三分法。*Timeout 变体接受 int $ms > 0,并以 TypeException 拒绝零 / 负数 / 非 int 输入。等待策略型 try* 方法要么返回一个 值类型的 Result,要么抛出领域异常——绝不用 null 编码。条件成功型 操作遵循专用的 setIfAbsent 命名,而非 try*
  • 没有 lensizependingtest 或其他临时起意的名称。
  • 领域特定的动词(evictdrainflush 等)只有在速查表中 没有任何规范条目涵盖该概念时才出现。

可观测性名称滞后于 PHP API

面向运维的接口——Prometheus 指标名称以及 /__ox_shared/entry?id=<id> 处的 JSON——是与 PHP API 分离的独立 契约。重命名它会破坏仪表盘和告警规则。为避免悄无声息的不一致, 受影响的名称会在一个发布周期内同时发出两份

接口 已弃用(仍在发出) 规范名称
Prometheus oxphp_shared_channel_pending oxphp_shared_channel_count
Prometheus oxphp_shared_pool_size oxphp_shared_pool_count
JSON entry Channel.pending Channel.count
JSON entry 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 之前,这些规则 同样具有约束力——违反它们的新方法会在评审中被拒绝。