# Symfony Lock — восемь опытов с параллельными PHP-процессами

Требования: PHP CLI >=8.2 с proc_open, Composer 2. Для всех восьми опытов — Docker Engine/Desktop + Compose v2; для пяти Flock-опытов Redis не нужен. Symfony Lock 7.4.23, Predis 2.4.1 и psr/log закреплены в composer.lock. Bootstrap 5.3.9 хранится локально. PHP-расширение redis не требуется: используется Predis. В Windows проверен PHP 8.3.33, в Linux-контейнере — PHP 8.3.36.

## Сначала посмотреть готовую мастерскую

```powershell
php -S 127.0.0.1:8512 -t public
```

Откройте http://127.0.0.1:8512. Здесь запись настоящих запусков, а не PHP/Redis в браузере. Кнопки переключают события, исходники и PHP-фрагменты. Для остановки HTTP-сервера нажмите Ctrl+C.

## Windows: повторить все опыты

Из корня распакованного проекта в PowerShell:

```powershell
php --version
composer install --no-interaction --no-plugins --prefer-dist
composer validate --strict
docker compose config --quiet
docker compose up -d --wait
composer capture
composer test
composer build-public
composer audit
composer serve
```

Redis публикуется только на 127.0.0.1:8533, интерфейс — на 127.0.0.1:8512. Если порт 8533 занят, измените mapping в compose.yaml и `$env:LOCK_REDIS_PORT='свободный_порт'`. Не останавливайте чужой процесс ради запуска примера.

Без Redis:

```powershell
composer capture:flock
php tests/run.php --flock-only
```

Пять Flock-опытов записываются отдельно в public/recording-flock.json; основная мастерская продолжает показывать приложенную полную recording.json. Это не замена полной записи частичной.

## Linux: те же команды

Установите PHP CLI >=8.2, Composer 2 и Docker Engine с Compose plugin по официальным инструкциям своего дистрибутива. Понадобится доступ вашей учётной записи к Docker. Не делайте chmod666 на Docker socket. Выполните те же composer/docker команды из корня проекта. PHP built-in server годится для локального просмотра, не для публичного сервиса.

Если выполняете capture в контейнере внутри сети Compose, задайте `LOCK_REDIS_HOST=redis`, `LOCK_REDIS_PORT=6379`. На Windows/Linux-хосте значения по умолчанию —127.0.0.1 и 8533. Настоящий публичный Redis здесь не нужен.

## Что именно выполняется

1. Два готовых PHP-процесса одновременно проходят барьер: один Flock acquire успешен, второй получает false.
2. B ждёт acquire(true), пока A освобождает общий ключ.
3. Разные ресурсы позволяют пересечение интервалов работы.
4. Исключение приводит в finally; следующий ожидающий процесс получает блокировку.
5. Контроллер принудительно завершает только созданный им дочерний процесс-владелец; Flock освобождается при закрытии дескриптора ОС. Lock-файл не удаляется.
6. RedisStore: тот же объект повторно получает lock, отдельный объект/процесс — нет; non-owner release не снимает владение.
7. TTL истёк: другой процесс получает ресурс, старый обнаруживает потерю, refresh выбрасывает исключение, запись отменяется.
8. Refresh продлевает аренду; конкурент получает отказ, новый worker после release проходит.

Capture запускает PHP массивом аргументов через proc_open, использует ready-файлы и общий go-barrier. Это настоящие отдельные процессы, не последовательное изменение одного объекта. События timestamp измеряются hrtime(true) на одном хосте. Микросекунды и длительности не предназначены для benchmark. Docker Redis — один экземпляр; отказоустойчивый кластер и сетевые разделения не моделируются.

В capture встроены проверки результатов 8 сценариев. `composer test` дополнительно проверяет хеши исходников, целостность событий, коды выхода и владение Flock. После изменения PHP запустите capture, build-public и test, чтобы запись соответствовала исходникам. Временные данные лежат в .runtime; public содержит только безопасные записи, исходники как .txt и документацию.

## Структура и применение

- src/Stores.php — явное создание Flock/Redis factory, таймауты Redis2 сек.
- bin/worker.php — bounded учебная работа и события.
- bin/capture.php — запуск конкурентов, проверка результата, запись JSON.
- public — статическая мастерская; её можно раздать Nginx/Caddy.
- docs/LINUX-VPS.md — отдельный практический runbook.
- examples/report.php — короткая CLI-команда с Flock, finally и exit code.
- deploy — systemd unit/timer и cron, выберите один планировщик.

Не размещайте весь репозиторий в web-root. Публикуйте только public; vendor, .runtime, Composer и рабочие данные остаются снаружи. Для общей блокировки нужны одинаковые ключи и одинаковое хранилище у всех участников. Flock в разных локальных каталогах контейнеров не становится общим автоматически.

## Остановка

Остановите HTTP-сервер Ctrl+C. Из каталога именно этого проекта выполните:

```sh
docker compose down
```

Это удаляет только контейнер/сеть aen-lock-b18, без глобального prune и без чужих volumes. Данные Redis в учебном стенде временные, сохранение отключено. Не используйте этот Compose вместо production Redis.

## Границы гарантий

TTL-lock не даёт exactly-once. Между isAcquired и внешней записью процесс может потерять аренду. Для строгой защиты ресурс проверяет монотонный fencing token либо используются подходящие транзакции/условная запись. Для повторов API и очередей нужны идемпотентность и UNIQUE. При недоступном store работу не следует продолжать без блокировки.

Исходники мастерской — MIT. Лицензии Symfony/Predis/psr/log/Bootstrap перечислены в public/THIRD-PARTY-LICENSES.txt. Лицензия контейнера Redis определяется выбранной версией Redis и не подменяется MIT нашего учебного кода.
