Files
elmer/doc/legacy/opus/opus-recheck-analysis-2026-06-28.md
T

68 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Opus — переанализ фаз 0–6 (28.06.2026)
Запрос: doc/opus-recheck-request-2026-06-28.md
---
## Вердикт
**Можно запускать на реальном ELM327 v1.5** для стендовых тестов с одним телефоном.
Логика протокола корректна. Один баг очереди надо закрыть до перехода на `gunicorn -w 4`.
---
## 1. ElmActor.kt — корректен, deadlock нет ✅
`init()` сабмитит `elm.init()` один раз и ждёт `.get(30s)`. Внутри `elm.init()` вызывает `elm.sendCommand("ATI")` прямым вызовом (не через `executor.submit`), то есть на том же потоке actor'а — рекурсивной постановки в очередь нет → deadlock невозможен. `isClone()` читает `@Volatile` поле напрямую — видимость корректна.
**Замечание (low):** при таймауте `.get(30, SECONDS)` Future не отменяется — задача `elm.init()` продолжает крутиться на executor'е, и следующий `send()` встанет за ней в очередь. Худший случай init по сумме `tryRead` ≈ 23с (ATSP0 один даёт до 10с) — близко к лимиту 30с. Рекомендация: `future.cancel(true)` в `catch(TimeoutException)` либо поднять лимит до 45с.
---
## 2. ElmProtocol.kt (raw) — порядок init верный, но есть двойной ATI ⚠️
Канонический порядок правильный: `ATE0→ATL0→ATS0→ATI→detectClone→[ATST96|ATAT1]→ATSP0`. Эхо выключается первым. Обработка двойного ответа ATWS корректна во всех ветках recovery/STOPPED/BUS ERROR.
Проблемы:
- **Двойной ATI (medium):** первый `write("ATI"); tryRead; drainInput()` бесполезен. Затем ещё раз `sendCommand("ATI")`. Python-версия сделана правильно (один `_exec("ATI")`).
- **MAX_RETRIES=6 даёт потолок 600мс, не 2000 (medium):** 6 шагов по +20 = 600мс. Если медленный ЭБУ требует >600мс — всё равно ошибка. Комментарий вводит в заблуждение.
- **exec() не перешлёт команду на ретрае:** пишет `cmd` один раз, при таймауте только перечитывает буфер. Для ELM штатно, но при реальном `NO DATA` повторное чтение не поможет.
---
## 3. raw_elm.py — SQLite-очередь: dequeue НЕ атомарен ❌ (главное)
`GET /api/v1/elm/raw/cmd` делает два отдельных стейтмента без guard'а. При `-w 4` два воркера могут выбрать одну строку → команда уйдёт на ELM дважды.
**Фикс:** `UPDATE ... SET status='sent' WHERE id=? AND status='pending'` + проверка `rowcount == 0`.
Прочие замечания:
- **Рост таблицы:** `command_queue` не чистится (старый код держал 500). Добавить `DELETE WHERE responded_at < datetime('now','-1 day')`.
- **`device_id="unknown"`:** несколько телефонов сольются в одну очередь.
- **`GET /response`:** при всплеске может вернуть ответ не на запрошенную команду (для пошагового relay'я ок).
---
## 4. Python protocol.py — паритет с Kotlin ✅
Тот же порядок, один ATI, ATST96 для клона, `try_read(500)` как drain после каждой AT-команды.
---
## 5. Безопасность (фаза 0) ✅
`api_key → env LLM_API_KEY`. Проверить что старый ключ отозван у провайдера.
---
## Итоговый список к исправлению (по приоритету)
1. ❌ **Атомарный dequeue** — guard `AND status='pending'` + `rowcount`
2. ⚠️ **Двойной ATI** в `ElmProtocol.kt init()` — убрать первый `write("ATI")`
3. ⚠️ **MAX_RETRIES/комментарий** — потолок 600мс, не 2000
4. **Ретеншн** `command_queue` + фильтрация `device_id`
5. **Отмена Future** при таймауте init в `ElmActor`
6. **Отозвать старый LLM-ключ** у провайдера
Пункты 2–5 не блокируют стендовый прогон. Dequeue-гонку держать в голове при `-w 4`.