Документированы ошибки AI (advisory lock, escName) и их исправления
This commit is contained in:
@@ -207,8 +207,12 @@ SVC_ID = 1 — фиксированный сервис (Болванка)
|
||||
|
||||
- **`_op_results`** — `threading.Lock()` вокруг всех операций чтения/записи/cleanup.
|
||||
`pop(k, None)` вместо `del dict[k]` — безопасно при конкурентном доступе.
|
||||
- **Advisory lock сценариев** — `pg_try_advisory_lock(hashtext('scenario:{clientId}:{stand}'))`
|
||||
атомарно проверяет и захватывает блокировку. `finally: unlock_scenario()` в `run_scenario()`.
|
||||
- **Partial unique index для сценариев** — `CREATE UNIQUE INDEX idx_one_running
|
||||
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`.
|
||||
Исключает lost-update между `add()` и `remove()` из разных воркеров gunicorn.
|
||||
|
||||
|
||||
@@ -188,3 +188,56 @@
|
||||
11. **In-memory `_op_results` на воркер** — не shared между gunicorn-воркерами.
|
||||
Статус иногда читается из API fallback. Решение: Redis/БД для статусов.
|
||||
Отложено — низкая вероятность проблемы на практике (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` при ошибке БД |
|
||||
|
||||
Reference in New Issue
Block a user