Документированы ошибки AI (advisory lock, escName) и их исправления

This commit is contained in:
2026-07-31 18:17:10 +04:00
parent d0f1edf401
commit 09d663d6d9
2 changed files with 59 additions and 2 deletions
+6 -2
View File
@@ -207,8 +207,12 @@ SVC_ID = 1 — фиксированный сервис (Болванка)
- **`_op_results`** — `threading.Lock()` вокруг всех операций чтения/записи/cleanup. - **`_op_results`** — `threading.Lock()` вокруг всех операций чтения/записи/cleanup.
`pop(k, None)` вместо `del dict[k]` — безопасно при конкурентном доступе. `pop(k, None)` вместо `del dict[k]` — безопасно при конкурентном доступе.
- **Advisory lock сценариев** — `pg_try_advisory_lock(hashtext('scenario:{clientId}:{stand}'))` - **Partial unique index для сценариев** — `CREATE UNIQUE INDEX idx_one_running
атомарно проверяет и захватывает блокировку. `finally: unlock_scenario()` в `run_scenario()`. ON scenario_runs (client_id, stand) WHERE status = 'RUNNING'`.
Делает `INSERT INTO scenario_runs ... status='RUNNING'` атомарной проверкой:
вторая параллельная вставка получает unique violation → 409.
Это заменило сломанную реализацию на `pg_try_advisory_lock` (v1.2.20),
где lock брался на одном соединении, а unlock — на другом (из пула).
- **Tracker** — `_atomic_update()`: read→mutate→write под одним `fcntl.flock`. - **Tracker** — `_atomic_update()`: read→mutate→write под одним `fcntl.flock`.
Исключает lost-update между `add()` и `remove()` из разных воркеров gunicorn. Исключает lost-update между `add()` и `remove()` из разных воркеров gunicorn.
+53
View File
@@ -188,3 +188,56 @@
11. **In-memory `_op_results` на воркер** — не shared между gunicorn-воркерами. 11. **In-memory `_op_results` на воркер** — не shared между gunicorn-воркерами.
Статус иногда читается из API fallback. Решение: Redis/БД для статусов. Статус иногда читается из API fallback. Решение: Redis/БД для статусов.
Отложено — низкая вероятность проблемы на практике (2 воркера, stickiness). Отложено — низкая вероятность проблемы на практике (2 воркера, stickiness).
---
## Повторный аудит Codex (2026-07-31, вторая итерация)
Codex проверил исправления и нашёл **критические ошибки в моих же фиксах**:
### ОШИБКА AI #1: Advisory lock сломан (v1.2.20)
**Что я сделал:** `lock_check()` брал `pg_try_advisory_lock` на соединении `conn1`,
возвращал `True`, и `conn1` уходил обратно в пул. `unlock_scenario()` вызывал
`get_conn()` → получал `conn2` (другое соединение!) → unlock на `conn2` не снимал
lock с `conn1`. Плюс ранние `return` в `api_scenario_run.py` после успешного
`lock_check` вообще не вызывали unlock.
**Почему ошибся:** не учёл что PostgreSQL advisory lock привязан к сессии (соединению),
а соединения возвращаются в пул. Передача соединения между `lock_check` и потоком
сценария требовала бы сложной оркестрации.
**Как исправлено (v1.2.21):** заменён на **partial unique index** на уровне БД:
```sql
CREATE UNIQUE INDEX idx_one_running
ON scenario_runs (client_id, stand) WHERE status = 'RUNNING';
```
Теперь `INSERT INTO scenario_runs ... status='RUNNING'` сам становится атомарной
проверкой — вторая вставка получает unique violation → 409 без гонок.
`lock_check` возвращён к простому SELECT (быстрая предпроверка для красивого 409).
`unlock_scenario` удалён полностью. `try/finally` из `run_scenario` убран.
### ОШИБКА AI #2: escName не экранирует `"` (v1.2.19)
**Что я сделал:** `def.name.replace(/\\/g,'\\\\').replace(/'/g,"\\'")`
экранировал `\` и `'` для JS-строки, но забыл `"` для HTML-атрибута `onclick="..."`.
**Почему ошибся:** фокусировался только на JS-контексте (строка в `'...'`),
не учёл что она внутри HTML-атрибута в `"..."`.
**Как исправлено (v1.2.21):** добавлено `.replace(/"/g,'"')` в `escName`.
### НОВАЯ находка Codex: instances.js:78
`instanceUid` в `onclick="toggleInstance('${i.instanceUid}')"` — теоретически уязвим,
но на практике UUID всегда `[a-f0-9-]+` → безопасен. Отмечен как низкий риск,
исправление не требуется.
### ИТОГ
| # | Слой | Статус |
|---|------|--------|
| Advisory lock | Python | ❌ СЛОМАН → ✅ partial unique index |
| escName `"` | JS | ❌ Неполный → ✅ добавлен `"` |
| instances.js onclick | JS | ⚠️ Низкий риск, UUID безопасен |
| `lock_check` fallback | Python | ✅ `True``False` при ошибке БД |