baldav/docs/session-logs/2026-09-29_startup-slowness.md
4aiman fd178ad97d Track detail: await history.go popstates before re-render after gap-fill revert/save
reopenDetail fired history.go(-count) then re-rendered synchronously, so the
async map/chart build raced the popstate unwind: the pending popstates
desynced the back stack and bumped the detail render token, and the render
bailed at the token check - map and charts came up blank until the next
event repainted them. Make reopenDetail async and await the popstate(s)
before showSessionDetail, the same resolve-on-popstate pattern as closeThen
(no setTimeout/setInterval).
2026-09-29 16:03:52 +03:00

11 KiB
Raw Blame History

Сессия 2026-09-29 - долгий старт БалДава + .gitignore

По утверждённому плану ~/.claude/plans/merry-discovering-dream.md (замерить -> починить).

Задача

  1. build.sh + BUILD_NUMBER в .gitignore (в build.sh пароль роутера, не место в git).
  2. Разобраться, почему БалДав стал долго стартовать, и починить.

.gitignore

Добавлены build.sh и BUILD_NUMBER (оба были untracked, не в игноре). Коммит 2d91c06.

Диагностика старта (по требованию - сначала замер)

Собирается только debug (release не рассматриваем), значит причина не в профиле сборки, а в работе на каждом старте. Замер на устройстве (adb am start -W + логи Tauri/Console, тайминг-логи временно добавлял в init/lib.rs, потом убрал):

  • am start -W TotalTime ~400ms (первый кадр - экран загрузки, быстро). Медленно - JS-init ПОСЛЕ первого кадра, он не виден в TotalTime.
  • В логах: [db.js] Repairing seq for 3 sessions... -> сессии 687 + 3614 + 10865 точек с seq IS NULL. Репейр (db.js runMigrations, ~2019) делал по одному UPDATE на точку - ~15000 последовательных IPC-round-trip'ов в debug-Rust. По таймингам одна сессия 3614 = ~3.8с, сессия 10865 не досчитывалась.
  • Ключевое (проверено через run-as cat baldav.db + sqlite3, journal_mode=delete, WAL ни при чём): репейр гонялся на КАЖДОМ старте (2 подряд без действий пользователя) и для крупных сессий seq НЕ сохранялся -> заново каждый запуск. Это и есть "долго": ~15с блокирующего init'а на старте.

Причина "стал": недавние merge/split (свежие фичи) создали NULL-seq сессии на 10865 и 3614 точек; поточечный репейр давился на них.

Фикс (frontend/js/db.js, репейр seq в runMigrations)

Поточечный цикл заменён на батчевую пере-нумерацию (CASE чанками по 300, паттерн saveGapFill/saveSmoothedTrack): один UPDATE на ~300 точек вместо одного на точку. Порядок - по ts (как и было). ~15000 invoke'ов -> ~50.

Проверка на устройстве (build 477, debug)

  • BOOT 1 (seq ещё NULL): репейр всех 3 сессий (15166 точек) за ~1.1с (было ~15с+); [BOOT] migrations = 1356ms.
  • BOOT 2 (без действий между): "checking seq repair" есть, "Repairing seq" НЕТ - nullSeqSessions пусто, репейр не запускался. [BOOT] migrations = 357ms. seq сохранился, репейр стал одноразовым.
  • Дамп БД после чистого force-stop: 0 NULL seq во всей БД; сессия 1789547934760 - seq 1..3614, инверсий по ts = 0 (порядок корректный, регрессии в отрисовке трека нет).

Осталось / отдельно

  • import_seed_products (lib.rs) на up-to-date-старте всё равно копирует сид 8.5МБ + гоняет дедуп = ~259ms каждый старт. Это НЕ было причиной "долго" (репейр был), поэтому seed-опт не делал: область products/dedup имела баги (FK), трогать без нужды рискованно. Вынес пользователю на решение.
  • Тайминг-обвязку (JS [BOOT], Rust [SEED-TIMING]) убрал; lib.rs/main.js вернулись к HEAD.

Пропажа тайлов карты на большом треке (отдельный баг)

Симптом: смотришь трек 60 км (сессия 1790501377655, 10865 точек), зумишь/таскаешь - тайлы карты пропадают, помогает только force-stop+старт.

Диагностика по живому logcat (пользователь воспроизвёл: открыл трек -> достроил -> сохранил -> reopen -> убрал достройку -> зум/таскание; буфер предварительно увеличил до 16МБ и очистил):

  • E chromium: [ERROR:cc/tiles/tile_manager.cc:1046] tile memory limits exceeded, some content may not draw - 40+ раз ровно в окно зум/таскания. Это лимит памяти тайлов КОМПОНОВЩИКА Chromium (не карты, не TileCache). Никаких OOM-килла, крахов рендера, CSP по тайлам.
  • Причина: трек рисуется дефолтным SVG-рендерером Leaflet (preferCanvas нигде не задан), да ещё многослойно (casing/core + раскраска по уклону мелкими сегментами). Гигантский SVG-слой на весь трек при зуме превышает бюджет растровой памяти -> компоновщик перестаёт рисовать часть, тайлы гаснут. Force-stop сбрасывает компоновщик. Проявляется на любом крупном треке/карте.

Фикс (frontend/js/main.js): preferCanvas: true на картах с тяжёлыми векторами (canvas рисует в один вьюпортный холст вместо тысяч SVG-путей):

  • showSessionDetail (деталь трека, исходный баг), showRoadsMap (карта дорог - много полилиний, пользователь отметил подтормаживания), showComparisonView (два трека), showRouteDetail (превью маршрута с раскраской).
  • НЕ трогал: renderRecordingInline (там rotate:true, а leaflet-rotate почти не поддерживает Canvas - 1 ссылка на L.Canvas против 5 на SVG; canvas+rotate рискован, отдельная проверка), редактор/рисование маршрута (клик/тап по линии, малые полилинии), цель-маркер (без треков).

Проверка: воспроизвести зум/таскание на треке 60км и на карте дорог, снять logcat, убедиться, что tile memory limits exceeded больше НЕ появляется, тайлы держатся.

Итог проверки canvas (build 479) - помог частично

Пользователь воспроизвёл на 479: "подвисло, но восстановилось" (раньше - только force-stop). Снял logcat + инспекция живой страницы через CDP (test_recovery/cdp.js, adb forward ... webview_devtools_remote_<pid>):

  • Canvas ПРИМЕНИЛСЯ: в DOM детали svgPaths=0, оверлей - canvas.
  • Но tile_manager tile memory limits exceeded всё равно ~46 раз (столько же). Значит бюджет растровых тайлов компоновщика забивают САМИ тайлы карты (высокий DPI: 1260 физ / 404 css ≈ 3x)
    • объём трека, а не SVG. Canvas дал живучесть (само восстанавливается), но джанк от 10865 сегментов остался.

Догоняющие правки (по решению пользователя: урезать тайлы + меньше точек на зум-ауте + POI)

  1. tilecache.js - у тайл-слоя keepBuffer: 1 (было 2) и updateWhenZooming: false: меньше офскрин-тайлов в памяти компоновщика, тайлы не грузятся в процессе зума.
  2. main.js - smoothFactor: 2.5 на главных полилиниях трека (деталь, showSessionDetail) и на сегментах карты дорог (showRoadsMap). smoothFactor - допуск Дугласа-Пекера в ПИКСЕЛЯХ, поэтому сам прореживает: при зум-ауте точки сходятся в пиксели и выбрасываются (меньше сегментов), при зум-ине - полная детализация. Ровно "меньше точек на сильном зум-ауте".
  3. poi.js - кап маркеров POI масштабируется зумом (z>=16: 500, 15: 200, 14: 80, 13: 30): на сильном зум-ауте показываем горстку ближайших, а не всё подряд.

Проверка: воспроизвести тот же сценарий, снять logcat - ожидаем, что tile memory limits exceeded резко упадёт/исчезнет, джанк уйдёт; POI на малом зуме - мало маркеров. Результат (build 480): пользователь - "всё ок".

Баг: после "убрать достройку" пусто (карта+графики) до следующего действия

Симптом: revert достройки -> графики и карта пропадают, из экрана трека не выкидывает, появляются только после следующего действия ("Отмена"). Стек окон + гонка.

Корень: reopenDetail (main.js:1176) стрелял history.go(-count) (асинхронные popstate) и СРАЗУ синхронно showSessionDetail. showSessionDetail пушит свою history-запись, потом отложенные popstate от go(-count) раскручивают уже ЕЁ и десинхронят стек; при fallthrough в renderScreen второй showSessionDetail бампает _sessionDetailRender, и первый async-рендер карты/графиков бросается на проверке токена (5411) -> пусто до следующего события. Ровно тот же класс, что чинил closeThen() (комментарий там про это).

Фикс: reopenDetail сделан async и ДОЖИДАЕТСЯ popstate от history.go перед showSessionDetail - через Promise + слушатель popstate (как closeThen), без setTimeout/setInterval. Проверка: revert на устройстве, по [SD]-логам один прогон showSessionDetail, карта+графики сразу на месте.