architect(ET): auto-commit from architect run_id=693
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
---
|
||||
work_item: ORCH-113
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-15
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# ADR-001: Reaper Tier-2 — in-memory ownership-маркер финализации `deploy-staging` (живой finalizer не реапится)
|
||||
|
||||
Work Item: **ORCH-113** — BUG: job-reaper повторно запускает финализацию `deploy-staging`, пока жив исходный finalizer
|
||||
Стадия: **architecture**
|
||||
Сквозная регистрация: **`docs/architecture/adr/adr-0043-reaper-finalizer-liveness-ownership.md`** (решение кросс-каттинговое — уточняет контракт reaper ORCH-065/adr-0011).
|
||||
|
||||
## Статус
|
||||
Proposed
|
||||
|
||||
## Контекст
|
||||
|
||||
Оркестратор self-hosting: один инстанс, общая БД/очередь, `max_concurrency=1`. Финальный статус job
|
||||
(`done`/`queued`/`failed`/`cancelled`) пишется **только** в живом процессе
|
||||
(`launcher._monitor_agent → _finalize_job`). Сверено по коду:
|
||||
|
||||
- `_monitor_agent` штампит `agent_runs.finished_at`/`exit_code` **ПЕРВЫМ** (`launcher.py:861`), затем
|
||||
делает git commit/push (+PR), usage-комментарии Plane (секунды…десятки секунд), затем
|
||||
`_try_advance_stage` (`launcher.py:998`) и лишь потом `_finalize_job` (`launcher.py:1003`).
|
||||
- На ребре `deploy-staging → deploy` `_try_advance_stage → advance_stage` синхронно, **в потоке
|
||||
монитора**, прогоняет тяжёлый набор edge-под-гейтов (`stage_engine.py:327–368`):
|
||||
`security` → `merge-gate` (полный локальный re-test, бюджет `merge_retest_timeout_s=900`) →
|
||||
`coverage` (`pytest --cov`) → `image-freshness` (docker-rebuild + пересоздание staging) — это
|
||||
**минуты**, и весь объём идёт **после** штампа `finished_at` и **до** `_finalize_job`.
|
||||
- Reaper Tier-2 (`job_reaper._reap_job`, `job_reaper.py:197–209`) меряет `finished_age_s` от
|
||||
`agent_runs.finished_at` (`db.get_running_jobs`, `db.py:1360`) = **от начала** финализации. По
|
||||
истечении `reaper_finalize_grace_s=300` он трактует живого, долго финализирующего монитора как
|
||||
мёртвого и независимо запускает тот же тяжёлый advance (`_reap_exit0 → _gate_driven_advance →
|
||||
_try_advance_stage → advance_stage`).
|
||||
|
||||
Дешёвая read-only пред-проверка `_gate_is_green('deploy-staging')` читает лишь `check_staging_status`
|
||||
(frontmatter `15-staging-log.md` = `SUCCESS`) → reaper уверенно идёт в тяжёлый advance. Атомарный
|
||||
claim-before-act (`reap_running_job ... WHERE status='running'`) защищает **флип строки** job, но **не
|
||||
side-effectful исполнение edge-гейтов**: монитор не claim'ит строку перед `advance_stage`, поэтому
|
||||
монитор и reaper выполняют `advance_stage` **параллельно**.
|
||||
|
||||
**Инцидент ORCH-111 (deployer job 1914, run_id 683):** финализация `deploy-staging` заняла >300с;
|
||||
reaper повторил edge-гейты; один повторный re-test стал красным (`3 failed … 14 errors in 444.79s`);
|
||||
задача ложно откатана `deploy-staging → development` (+ ложный developer-retry), **параллельно**
|
||||
исходный finalizer довёл deploy до `SUCCESS` и смержил PR #130 (`deploy → done`). Состояние раздвоилось.
|
||||
|
||||
Источника истины «жив ли finalizer» сегодня нет: pid агента в Tier-2 уже мёртв в **обоих** случаях
|
||||
(`proc.wait()` вернулся), а живость **потока-монитора** система не наблюдает. Per-stage grace,
|
||||
покрывающая Σ финализации (`Σ ≈ 4160с`), невозможна без нарушения сквозного бюджета ORCH-065/109/110
|
||||
`reaper_max_running_s (5400) > Σ(deploy-staging gate-work) + grace (≈4460)`.
|
||||
|
||||
**Решающий факт (проверен):** монитор и reaper — daemon-**потоки одного** uvicorn-процесса (CMD без
|
||||
`--workers`; `_monitor_agent` стартует `threading.Thread`, `launcher.py:661`; reaper — daemon-поток,
|
||||
`main.py:144`), разделяющие одну SQLite-БД. Значит, живость finalizer'а можно определить **in-memory**.
|
||||
Рестарт покрыт существующим `requeue_running_jobs()` (`running → queued`), который в `main.lifespan`
|
||||
вызывается (`main.py:59`) **до** старта reaper (`main.py:144`).
|
||||
|
||||
## Решение
|
||||
|
||||
### Сводка
|
||||
Ввести **процесс-локальный реестр владения финализацией**: живой монитор регистрирует «я финализирую
|
||||
job X», а reaper в Tier-2 на стадии `deploy-staging` **не реапит** job, чьё владение активно, и
|
||||
переходит к Tier-3 backstop. Реестр in-memory — авторитетен в рамках одного процесса/БД; рестарт
|
||||
покрыт `requeue_running_jobs`. Grace и `reaper_max_running_s` не меняются → сквозной бюджет цел. Под
|
||||
глобальным kill-switch; **нулевое** изменение схемы БД и контрактов.
|
||||
|
||||
### D1 — Leaf `src/finalizer_liveness.py` (владение, FR-2)
|
||||
Новый чистый процесс-локальный модуль (паттерн `serial_gate`/`coverage_gate`: never-raise, без сети/БД):
|
||||
- `mark(job_id, run_id, stage)` — зарегистрировать активную финализацию;
|
||||
- `clear(job_id)` — снять;
|
||||
- `is_active(job_id) -> bool` — есть ли живое владение;
|
||||
- `snapshot() -> dict` — read-only для наблюдаемости.
|
||||
|
||||
Состояние — `{job_id: {"run_id", "stage", "started_ts"}}` + `threading.Lock`. Собственного TTL нет —
|
||||
ограничение по времени даёт Tier-3 (см. D3). Все функции изолированы `try/except` → дефолт
|
||||
(`is_active` при ошибке → `False`, консервативно: не блокировать добивание).
|
||||
|
||||
### D2 — Эмиссия владения в `launcher._monitor_agent` (FR-1)
|
||||
`mark(job_id, run_id, stage)` вызывается **сразу после** штампа `exit_code` (`launcher.py:864`, самый
|
||||
ранний момент, когда reaper переходит в Tier-2; до этого pid агента жив → Tier-1 защищает). Хвост
|
||||
финализации (git push … `_try_advance_stage` … `_finalize_job`) оборачивается в `try/finally`, в
|
||||
`finally` — `clear(job_id)`. Так исключение **в потоке монитора** гарантированно снимает владение →
|
||||
reaper добивает (FR-4). Только при `job_id is not None` (legacy `launch()` с `job_id=None` не в
|
||||
`get_running_jobs`). Гибель **всего процесса** → рестарт → `requeue_running_jobs` → реестр пуст
|
||||
(restart-safe без durable, NFR-5).
|
||||
|
||||
### D3 — Консультация reaper, scoped + Tier-3 backstop (FR-3, FR-4)
|
||||
В `job_reaper._reap_job`, Tier-2-ветка (`exit_code` записан, `finished_age >= grace`): **перед**
|
||||
`_reap_known_outcome` — если `settings.reaper_finalizer_liveness_enabled` **И** стадия задачи
|
||||
(`_task_meta`) `== "deploy-staging"` **И** `finalizer_liveness.is_active(job_id)` → **defer** (лог +
|
||||
счётчик), **не** реапить через Tier-2, провалиться к Tier-3. Иначе — прежний путь
|
||||
(`_reap_known_outcome; return`), байт-в-байт. **Tier-3** (`age >= reaper_max_running_s`) маркер
|
||||
**игнорирует** — добивает всегда (ограниченное время; бюджет `5400 > Σ+grace ≈ 4460` гарантирует, что
|
||||
легитимная финализация завершится до 5400 → ложного Tier-3-реапа живого finalizer'а нет).
|
||||
|
||||
### D4 — Скоуп и kill-switch (NFR-4)
|
||||
Только глобальный `reaper_finalizer_liveness_enabled` (`config.py`, env
|
||||
`ORCH_REAPER_FINALIZER_LIVENESS_ENABLED`, дефолт `True`). **Без** per-repo разреза: баг общий для
|
||||
любого репо со стадией `deploy-staging` (enduro тоже); per-repo оставил бы баг активным для части
|
||||
репо. Это сознательный отход от leaf-паттерна `*_repos` (он для **гейтов, действующих на репо**; здесь
|
||||
— наблюдатель-безопасность глобального демона). `False` → reaper никогда не консультирует маркер →
|
||||
поведение байт-в-байт прежнее; стадии `!= deploy-staging` не консультируются → не тронуты.
|
||||
|
||||
### D5 — Наблюдаемость (TZ §4)
|
||||
Счётчик `finalizer_defers_total` + размер `finalizer_liveness.snapshot()` в блоке `reaper`
|
||||
`GET /queue`. Существующие ключи ответа не меняются; новых эндпоинтов нет.
|
||||
|
||||
### Инварианты
|
||||
`STAGE_TRANSITIONS` / `QG_CHECKS` / каждый `check_*` / machine-verdict ключи / схема существующих
|
||||
таблиц — **байт-в-байт**; **нулевое** изменение схемы БД; reaper остаётся never-raise per-unit;
|
||||
`reaper_finalize_grace_s` и `reaper_max_running_s` **не меняются** (NFR-6 цел); фикс не рестартит прод
|
||||
и не пушит `main` (NFR-3). Merge-verify (`deploy → done`, ORCH-071) — единственный choke-point в
|
||||
`done`, не ослабляется (FR-5).
|
||||
|
||||
## Альтернативы
|
||||
- **Per-stage grace, покрывающая Σ** — отвергнуто: нарушает бюджет `5400 > Σ+grace` (Σ≈4160 ⇒ grace
|
||||
пришлось бы <1240, не покрывает Σ); таймер — это и есть источник бага.
|
||||
- **Durable-колонка (finalizing-heartbeat / owner-токен)** — отвергнуто: один процесс/одна БД →
|
||||
in-memory авторитетно; рестарт покрыт requeue; блокирующий re-test (900с) не может бить периодический
|
||||
heartbeat из того же потока; durable добавляет миграцию и запись ради нулевой выгоды.
|
||||
- **Sub-state `finalizing` в `jobs.status`** — отвергнуто: меняет семантику статуса, который читают
|
||||
`claim_next_job`/`requeue_running_jobs`/`reconciler`/`reaper` (`WHERE status='running'`) — нарушение
|
||||
NFR-2 и высокий радиус поражения.
|
||||
- **Lease-файл на `(job, stage)` (как merge-lease)** — отвергнуто: тяжелее (файловый I/O, TTL,
|
||||
reclaim), дублирует merge-lease; in-memory достаточно при одном процессе; TTL возвращает таймер-проблему.
|
||||
- **Флип job из `running` до тяжёлых гейтов** — отвергнуто: ломает `get_running_jobs`/метрики и
|
||||
restart-requeue (краш мид-гейт оставит job non-running и нереквью'имым).
|
||||
|
||||
## Последствия
|
||||
- **+** Устранены повторный прогон edge-гейтов, ложный откат `deploy-staging → development` и
|
||||
расхождение состояния при живом долгом finalizer'е; идемпотентность edge-гейтов через владение
|
||||
(AC-1/AC-2/AC-4).
|
||||
- **+** Реально мёртвый/застрявший finalizer добивается (finally-clear → Tier-2; иначе Tier-3);
|
||||
основная функция reaper ORCH-065 сохранена (AC-3).
|
||||
- **+** Нулевое изменение схемы и контрактов; сквозной бюджет ORCH-065/109/110 не тронут (AC-5).
|
||||
- **−** Гарантия владения валидна при **одном процессе/одной БД** (проверено: один uvicorn-воркер);
|
||||
ввод `--workers>1` потребует durable-сигнала — зафиксировано в `10-tech-risks.md` (TR-3).
|
||||
- **−** Окно «штамп `finished_at` → `mark()`» (git push) маркером не покрыто — закрыто прежним
|
||||
grace=300 (окно ≪ grace), TR-2.
|
||||
- **Откат:** `ORCH_REAPER_FINALIZER_LIVENESS_ENABLED=false` → reaper байт-в-байт прежний (маркер
|
||||
по-прежнему пишется монитором, но не консультируется — инертен). Полный откат — удаление leaf +
|
||||
двух врезок.
|
||||
|
||||
## Ссылки
|
||||
- BRD: `docs/work-items/ORCH-113/01-brd.md`
|
||||
- TRZ: `docs/work-items/ORCH-113/02-trz.md`
|
||||
- Acceptance: `docs/work-items/ORCH-113/03-acceptance-criteria.md`
|
||||
- Test-plan: `docs/work-items/ORCH-113/04-test-plan.yaml`
|
||||
- Сквозной ADR: `docs/architecture/adr/adr-0043-reaper-finalizer-liveness-ownership.md`
|
||||
- Базовые ADR: `adr-0011` (reaper/lease ORCH-065), `adr-0040` (timeout-бюджеты ORCH-109),
|
||||
`adr-0042` (merge-gate re-test ORCH-110)
|
||||
- Сверено по коду: `src/job_reaper.py`, `src/agents/launcher.py` (`_monitor_agent`/`_try_advance_stage`),
|
||||
`src/db.py` (`get_running_jobs`/`requeue_running_jobs`), `src/stage_engine.py` (`advance_stage` ребро
|
||||
`deploy-staging`), `src/config.py` (`reaper_*`), `src/main.py` (`lifespan`)
|
||||
</content>
|
||||
40
docs/work-items/ORCH-113/07-infra-requirements.md
Normal file
40
docs/work-items/ORCH-113/07-infra-requirements.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
work_item: ORCH-113
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-15
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 07 — Инфра-требования: ORCH-113 — reaper finalizer-liveness ownership
|
||||
|
||||
Work Item: **ORCH-113** · Repo: **orchestrator** · Стадия: architecture
|
||||
|
||||
> When-applicable / информационный. Топология **не меняется**; ниже — только конфиг и операционные
|
||||
> инварианты, которые сопровождающий обязан удержать.
|
||||
|
||||
## Изменения топологии
|
||||
**N/A.** Ни новых сервисов/контейнеров, ни портов, ни томов, ни сетевых правил. Решение целиком внутри
|
||||
процесса `orchestrator` (новый leaf + две врезки в существующие потоки monitor/reaper).
|
||||
|
||||
## Новый конфиг (env)
|
||||
| Ключ | Дефолт | Назначение |
|
||||
|------|--------|-----------|
|
||||
| `ORCH_REAPER_FINALIZER_LIVENESS_ENABLED` | `true` | Kill-switch. `false` → reaper байт-в-байт прежний (маркер пишется, но не консультируется). Откат фикса = установить `false`. |
|
||||
|
||||
Существующие `reaper_finalize_grace_s` (300) и `reaper_max_running_s` (5400) — **не меняются**.
|
||||
`.env.example` пополнить новым ключом (дефолт = боевое значение, паттерн ORCH-101: пустой `.env` ⇒
|
||||
прежнее поведение).
|
||||
|
||||
## Операционные инварианты (сопровождение)
|
||||
- **Одно-процессная модель — несущий инвариант.** Авторитетность in-memory реестра владения держится
|
||||
на том, что монитор и reaper — потоки **одного** uvicorn-процесса. CMD/команда compose **не должны**
|
||||
получать `uvicorn --workers>1` без перевода сигнала в durable (см. `10-tech-risks.md` TR-3, ADR-001).
|
||||
Сверено: `Dockerfile:65`, `docker-compose.yml:36` (prod), `docker-compose.yml:123` (staging) — без
|
||||
`--workers`.
|
||||
- **Сквозной бюджет ORCH-065/109/110** `reaper_max_running_s (5400) > Σ(deploy-staging gate-work)+grace
|
||||
(≈4460)` остаётся в силе и фиксом не затрагивается (TR-4).
|
||||
- **Self-hosting-страховка:** обкатка — на staging (8501, изолированная БД) до прод-деплоя; деплой
|
||||
орка — только через статус «Confirm Deploy». Фикс не рестартит прод и не пушит `main`.
|
||||
</content>
|
||||
43
docs/work-items/ORCH-113/08-data-requirements.md
Normal file
43
docs/work-items/ORCH-113/08-data-requirements.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
work_item: ORCH-113
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-15
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 08 — Требования к данным: ORCH-113 — reaper finalizer-liveness ownership
|
||||
|
||||
Work Item: **ORCH-113** · Repo: **orchestrator** · Стадия: architecture
|
||||
|
||||
> When-applicable / информационный (гейтом не парсится).
|
||||
|
||||
## Изменения схемы БД
|
||||
|
||||
**N/A — нулевое изменение схемы.** Сознательное архитектурное решение (ADR-001 / adr-0043): сигнал
|
||||
владения финализацией — **in-memory** (leaf `src/finalizer_liveness.py`), а не durable-колонка. Ни
|
||||
новых таблиц, ни новых колонок, ни индексов; `init_db()` / `_ensure_column` не трогаются. Схема
|
||||
существующих таблиц (`jobs`, `agent_runs`, `tasks`, …) и их семантика — **байт-в-байт** (NFR-2/AC-5).
|
||||
|
||||
## Новые/изменённые сущности
|
||||
|
||||
**Процесс-локальный реестр владения** (не БД): `finalizer_liveness` хранит
|
||||
`{job_id: {"run_id", "stage", "started_ts"}}` под `threading.Lock`. Запись/снятие — живой
|
||||
монитор-поток (`launcher._monitor_agent`); чтение — reaper-поток (`job_reaper`). Ключ — `jobs.id`
|
||||
(существующая сущность). Никаких новых персистентных данных.
|
||||
|
||||
## Совместимость данных / миграции
|
||||
|
||||
- **Миграций нет** — нечего мигрировать (нет схемных изменений); общая прод-БД (self-hosting +
|
||||
enduro-trails) не затрагивается.
|
||||
- **Restart-safe без durable (NFR-5):** in-memory реестр сбрасывается при рестарте процесса, что
|
||||
**безопасно** по существующему контракту: `main.lifespan` вызывает `requeue_running_jobs()`
|
||||
(`running → queued`, `main.py:59`) **до** старта reaper (`main.py:144`). После рестарта нет ни одного
|
||||
`running`-job, ссылающегося на потерянный маркер → отсутствие маркера корректно (нет живых
|
||||
finalizer'ов). Гибель **потока** монитора (не процесса) покрыта `try/finally`-снятием маркера; гибель
|
||||
**процесса** → рестарт → requeue.
|
||||
- **Авторитетность in-memory** опирается на одно-процессную модель (один uvicorn-воркер, общая
|
||||
SQLite-БД; проверено: CMD без `--workers`). Условие задокументировано как инвариант сопровождения —
|
||||
при вводе `--workers>1` сигнал должен стать durable (см. `10-tech-risks.md` TR-3).
|
||||
</content>
|
||||
37
docs/work-items/ORCH-113/10-tech-risks.md
Normal file
37
docs/work-items/ORCH-113/10-tech-risks.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
work_item: ORCH-113
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-15
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 10 — Технические риски: ORCH-113 — reaper finalizer-liveness ownership
|
||||
|
||||
Work Item: **ORCH-113** · Repo: **orchestrator** · Стадия: architecture
|
||||
|
||||
> Информационный (гейтом не парсится). Риски реализации и их митигейшн.
|
||||
|
||||
## Реестр рисков
|
||||
|
||||
| ID | Риск | Вер. | Влия. | Митигейшн |
|
||||
|----|------|------|-------|-----------|
|
||||
| TR-1 | **Над-толерантность:** маркер «жив» застрял (не снят) → реально мёртвый finalizer не добивается, зомби клинит очередь (регресс ORCH-065). | Низ. | Выс. | `try/finally`-снятие в `_monitor_agent` (исключение потока снимает владение); гибель процесса → рестарт → `requeue_running_jobs`. **Tier-3 backstop игнорирует маркер** и добивает при `age >= reaper_max_running_s=5400` → ограниченное время гарантировано (FR-4/AC-3). Покрытие — TC-03. |
|
||||
| TR-2 | **Окно без владения** между штампом `finished_at` (launcher:861) и `mark()` (после exit_code, launcher:864): reaper мог бы реапнуть в этом окне. | Низ. | Сред. | Окно = git push/PR/Plane-комментарии (секунды…десятки секунд) ≪ `reaper_finalize_grace_s=300` → прежний grace покрывает его; маркер ставится самым ранним возможным моментом Tier-2 (до этого pid агента жив → Tier-1 защищает). |
|
||||
| TR-3 | **Многопроцессность:** при `uvicorn --workers>1` монитор и reaper окажутся в разных процессах → in-memory реестр не разделяется → возможна двойная финализация. | Низ. | Выс. | Сейчас CMD без `--workers` (проверено: `Dockerfile:65`, `docker-compose.yml:36`). Инвариант сопровождения зафиксирован в ADR-001/adr-0043 и 08-data-requirements: ввод `--workers>1` ⇒ перевести сигнал в durable (heartbeat-колонка) — отдельная задача. Анти-дрейф можно усилить структурным тестом (нет `--workers` в CMD). |
|
||||
| TR-4 | **Нарушение сквозного бюджета** ORCH-065/109/110 при правке grace/таймаутов. | Оч. низ. | Выс. | Решение **не меняет** `reaper_finalize_grace_s` (300) и `reaper_max_running_s` (5400) — инвариант `5400 > Σ(deploy-staging gate-work)+grace ≈ 4460` тривиально цел; покрытие — TC-07/AC-5. |
|
||||
| TR-5 | **Гонка чтения/записи** реестра (монитор пишет, reaper читает). | Низ. | Сред. | `threading.Lock` вокруг операций реестра; `is_active`/`snapshot` атомарны под локом; never-raise → ошибка чтения = `False` (консервативно, не блокирует добивание). Покрытие — TC-02/TC-04/TC-08. |
|
||||
| TR-6 | **Регресс не-deploy-staging / выключенного флага** (NFR-4): фикс случайно меняет прежние пути reaper. | Низ. | Сред. | Консультация маркера gated `enabled AND stage=="deploy-staging"`; Tier-1/Tier-3/exit≠0/claim-before-act не трогаются; `False` → reaper байт-в-байт прежний. Покрытие — TC-06. |
|
||||
| TR-7 | **Ложный regression-тест** TC-05: зелёный и до фикса (не воспроизводит баг). | Сред. | Сред. | TC-05 моделирует «живой долгий finalizer > grace» управляемо (моки подпроцессов/сети); обязан быть **красным до** фикса и **зелёным после** (AC-6). Reviewer/tester проверяют красноту на базе. |
|
||||
|
||||
## Сводный вывод
|
||||
|
||||
Доминирующий класс — **над-толерантность** (TR-1) и **многопроцессная авторитетность** (TR-3); оба
|
||||
имеют низкую вероятность и закрыты соответственно Tier-3 backstop'ом (без правки бюджета) и
|
||||
зафиксированным инвариантом одно-процессной модели. Решение аддитивно, под kill-switch, без изменения
|
||||
схемы/контрактов и без правки сквозного бюджета. Эскалация `arch:major-change` **не требуется**
|
||||
(нет новой стадии/QG, нет изменения схемы БД, центр тяжести — один leaf + две точечные врезки).
|
||||
Остаточный риск для прод-конвейера (self-hosting) — **низкий**; полностью обратим выключением
|
||||
`ORCH_REAPER_FINALIZER_LIVENESS_ENABLED`.
|
||||
</content>
|
||||
Reference in New Issue
Block a user