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

php
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
<?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
<?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
<?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
<?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 — когда переключение флага должно фиксироваться вместе с другим состоянием.