Shared\Flag
OxPHP\Shared\Flag — это атомарное булево значение, действующее в пределах всего процесса, булев близнец Shared\Atomic. Каждая операция является lock-free; два воркера, одновременно переключающие флаг, не могут наблюдать промежуточное состояние.
Обзор
- Атомарный bool. Единственный бит состояния с операциями
load/store/swap/compareAndSet. - Явное упорядочивание обращений к памяти. Каждая операция принимает необязательный параметр
Orderingсо значением по умолчаниюSeqCst, точно так же, какShared\Atomic. - Lock-free. Все изменения — это одна атомарная инструкция процессора. Безопасно в условиях конкуренции за доступ.
- Разделяемый (Shareable). Экземпляры живут в реестре и могут храниться внутри
Shared\Map, передаваться через захватыuseи т.д.
Справочник по API
namespace OxPHP\Shared;
final class Flag implements Shareable
{
public function __construct(bool $initial = false);
public function load(Ordering $order = Ordering::SeqCst): bool; // Relaxed | Acquire | SeqCst
public function store(bool $value, Ordering $order = Ordering::SeqCst): void; // Relaxed | Release | SeqCst
public function swap(bool $value, Ordering $order = Ordering::SeqCst): bool; // any ordering; returns previous
public function compareAndSet(
bool $expect,
bool $new,
Ordering $success = Ordering::SeqCst,
Ordering $failure = Ordering::SeqCst, // Relaxed | Acquire | SeqCst
): bool;
public function id(): int;
}| Method | Returns | Use case |
|---|---|---|
load |
текущее | Простое чтение. |
store |
void | Безусловно устанавливает заданное значение. |
swap |
предыдущее | Устанавливает заданное значение; возвращаемое значение говорит, изменили ли вы его. swap(true) — это test-and-set («выиграл ли я?»). |
compareAndSet |
поменяли? | Одноразовая инициализация: успешна только если флаг имел ожидаемое значение. |
Примеры
Kill-switch
<?php
use OxPHP\Shared\Flag;
$maintenance = new Flag();
// In a request handler
if ($maintenance->load()) {
http_response_code(503);
header('Retry-After: 60');
echo 'under maintenance';
return;
}
// In an admin endpoint
$maintenance->store(true); // enable
$maintenance->store(false); // disableПобедитель одноразовой инициализации
<?php
use OxPHP\Shared\Flag;
$migrated = new Flag();
if ($migrated->compareAndSet(expect: false, new: true)) {
// First worker to arrive wins — run the migration once.
runSchemaMigration();
} else {
// Someone else already ran it.
}Срабатывание circuit breaker
<?php
use OxPHP\Shared\Flag;
$tripped = new Flag();
try {
callDownstream();
} catch (DownstreamFailedException $e) {
$wasAlreadyTripped = $tripped->swap(true); // set true, learn the prior state
if (!$wasAlreadyTripped) {
alertOncall($e); // fire alert only on first trip
}
throw $e;
}Для полноценного circuit breaker вам обычно понадобятся Shared\Counter для окна отслеживания сбоев и Shared\Flag для состояния срабатывания — сбрасывайте флаг через store(false), как только окно «остынет».
Опубликуйте полезную нагрузку, затем подайте сигнал с более дешёвым упорядочиванием
<?php
use OxPHP\Shared\Flag;
use OxPHP\Shared\Map;
use OxPHP\Shared\Ordering;
$ready = new Flag();
$config = new Map();
// Producer: write the payload, then publish with Release.
$config->set('dsn', $dsn);
$ready->store(true, Ordering::Release);
// Consumer: an Acquire load that observes `true` also observes the payload.
if ($ready->load(Ordering::Acquire)) {
$dsn = $config->get('dsn');
}Семантика и подводные камни
swap возвращает предыдущее значение — это самый полезный результат: «изменил ли я что-нибудь?» — это $prev !== $new, а swap(true) — это канонический test-and-set. store возвращает void; если вам нужно предыдущее значение, используйте swap.
compareAndSet — это способ выразить «первый побеждает». Обычный store(true) всегда успешен, поэтому он не может выразить «не перезаписывать, если уже установлено».
Упорядочивание обращений к памяти совпадает с Shared\Atomic. load отклоняет Release/AcqRel, store отклоняет Acquire/AcqRel, а $failure у compareAndSet отклоняет Release/AcqRel — каждый случай приводит к InvalidOrderingException. Значение по умолчанию SeqCst всегда безопасно.
Flag не блокирует. Если вам нужно дождаться перехода, скомбинируйте его с Shared\Channel или используйте Shared\Once.
Исключения
| Exception | Raised by |
|---|---|
StaleHandleException |
Любой метод дескриптора, запись которого была вытеснена из реестра. |
UninitializedException |
id() на обёртке, которая не завершила __construct. |
InvalidOrderingException |
Ordering, недопустимое для данной операции (см. выше). |
Наблюдаемость
См. Shared Observability. Краткая справка:
GET /__ox_shared/entry?id=Nотдаёт{ value: true|false, type: "Flag" }.- Prometheus-метрика (gauge)
oxphp_shared_flag_value{flag_id="…"}(0 или 1). - Метрики уровня всего реестра охватывают Flag через метку
type="Flag".
Когда не стоит использовать
- Логика с несколькими состояниями. Flag принимает лишь два значения. Если вам нужны idle/busy/done или любой автомат с тремя состояниями, обратитесь к
Shared\Counter(используя целочисленные значения перечисления) илиShared\Mutexповерх массива, похожего на перечисление. - Ожидание перехода. Флаги не блокируют. Скомбинируйте с
Shared\Channel(или сShared\Counter, который вы опрашиваете черезcompareAndSet), когда воркер должен ждать, пока флаг переключится. - Подсчёт событий. Flag — это не счётчик. Используйте
Shared\Counterдля подсчётов. - Целочисленное состояние. Если переключатель на самом деле является небольшим целым числом, используйте
Shared\Atomicнапрямую.
Связанные материалы
- Shared State — обзор и ментальная модель.
- Shared\Atomic — близнец на основе int64, та же модель упорядочивания.
- Shared\Counter — когда нужно больше, чем вкл/выкл.
- Shared\Once — когда однажды вычисленное значение богаче, чем bool.
- Shared\Mutex — когда переключение флага должно фиксироваться вместе с другим состоянием.