Skip to content

fix(glossary): один ответ sqlite о файле — снимок, а не приговор - #1540

Merged
ArtVsMark merged 3 commits into
mainfrom
agent/queue-creation-race-is-closed
Sep 9, 2026
Merged

ArtVsMark merged 3 commits into
mainfrom
agent/queue-creation-race-is-closed

Conversation

@ArtVsMark

Copy link
Copy Markdown
Owner

Конкурентное создание очереди теряло добавки: воркер падал
sqlite3.OperationalError: disk I/O error на COMMIT внутри
apply_schema. Отказ пришёл с ubuntu-latest × 3.14 и стал виден
только потому, что ячейка перестала быть экспериментальной (#1529) —
до этого падение шло под continue-on-error и сводки не порождало.

ОКНО. _ensure_queue_db решает судьбу файла тремя шагами: чтение первых
байтов → спросить sqlite → legacy-ветка с карантином. Фикс #924 закрыл
окно между первым и решением. Осталось окно ВНУТРИ второго:
sqlite3.connect создаёт файл сразу, а заголовок дописывает позже, и в
этот промежуток сосед законно отвечает DatabaseError. Приняв такой
ответ за приговор, мы уводили живую базу в .corrupt из-под открытого
дескриптора — владелец и получал disk I/O error.

ПОЧИНКА. _sqlite_accepts_settled переспрашивает: файл, который сосед
дописывает, станет базой за микросекунды, чужой формат не станет им
никогда. Бюджет намеренно копеечный (5 попыток по 20 мс) — это «дать
дописать заголовок», а не «ждать, пока починится». Пустой файл базой
считается сразу: ноль байт не бывает ни legacy JSON, ни осмысленным
мусором — это чужое connect мгновение назад.

ДОКАЗАТЕЛЬСТВО. Гонка воспроизведена ДЕТЕРМИНИРОВАННО, без ожидания
редкого совпадения: тест возвращает тот ответ, который sqlite законно
даёт в середине чужого connect. Красный до фикса проверен прогоном —
с одиночной проверкой падает, с переспросом проходит. Живой гонкой
воспроизвести не удалось: на 3.12 с libsqlite 3.45.1 при 16 процессах
ноль отказов, а 3.14 в контейнере нет.

СОСЕДИ (правило 195). Второй потребитель db.connect —
core/history.py. Ветки карантина у него нет: то же окно даёт не потерю
данных, а ложное сообщение «файл повреждён» (маркер not a database в
_CORRUPT_MARKERS). Данные там не разрушаются, поэтому правка отдельная
и не срочная.

Closes #1533

Работа сделана вместе: @ArtVsMark — постановка, решения и приёмка; Claude Code — реализация.

Конкурентное создание очереди теряло добавки: воркер падал
`sqlite3.OperationalError: disk I/O error` на `COMMIT` внутри
`apply_schema`. Отказ пришёл с `ubuntu-latest × 3.14` и стал виден
только потому, что ячейка перестала быть экспериментальной (#1529) —
до этого падение шло под `continue-on-error` и сводки не порождало.

ОКНО. `_ensure_queue_db` решает судьбу файла тремя шагами: чтение первых
байтов → спросить sqlite → legacy-ветка с карантином. Фикс #924 закрыл
окно между первым и решением. Осталось окно ВНУТРИ второго:
`sqlite3.connect` создаёт файл сразу, а заголовок дописывает позже, и в
этот промежуток сосед законно отвечает `DatabaseError`. Приняв такой
ответ за приговор, мы уводили живую базу в `.corrupt` из-под открытого
дескриптора — владелец и получал `disk I/O error`.

ПОЧИНКА. `_sqlite_accepts_settled` переспрашивает: файл, который сосед
дописывает, станет базой за микросекунды, чужой формат не станет им
никогда. Бюджет намеренно копеечный (5 попыток по 20 мс) — это «дать
дописать заголовок», а не «ждать, пока починится». Пустой файл базой
считается сразу: ноль байт не бывает ни legacy JSON, ни осмысленным
мусором — это чужое `connect` мгновение назад.

ДОКАЗАТЕЛЬСТВО. Гонка воспроизведена ДЕТЕРМИНИРОВАННО, без ожидания
редкого совпадения: тест возвращает тот ответ, который sqlite законно
даёт в середине чужого `connect`. Красный до фикса проверен прогоном —
с одиночной проверкой падает, с переспросом проходит. Живой гонкой
воспроизвести не удалось: на 3.12 с libsqlite 3.45.1 при 16 процессах
ноль отказов, а 3.14 в контейнере нет.

СОСЕДИ (правило 195). Второй потребитель `db.connect` —
`core/history.py`. Ветки карантина у него нет: то же окно даёт не потерю
данных, а ложное сообщение «файл повреждён» (маркер `not a database` в
`_CORRUPT_MARKERS`). Данные там не разрушаются, поэтому правка отдельная
и не срочная.

Closes #1533

Co-Authored-By: Artem Markitanov <86671904+ArtVsMark@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wqpt9etxi7Hz6dNyhmYfx1
@ArtVsMark ArtVsMark added the merge-when-green Согласие смержить без автора: авто-мерж включится, как позеленеет label Sep 9, 2026
@ArtVsMark
ArtVsMark enabled auto-merge (squash) September 9, 2026 10:19
@claude claude Bot added area/glossary Глоссарий и coverage (glossary/) bug Something isn't working labels Sep 9, 2026
Co-Authored-By: Artem Markitanov <86671904+ArtVsMark@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wqpt9etxi7Hz6dNyhmYfx1
Co-Authored-By: Artem Markitanov <86671904+ArtVsMark@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wqpt9etxi7Hz6dNyhmYfx1
@claude claude Bot added the blocker label Sep 9, 2026
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Ставлю blocker: main красная, и этот PR — чинящий.

Что на main. Прогон 34347856000 (мерж #1537): упала обязательная test (windows-latest, 3.12, false). Артефакта test-results-windows-latest-3.12 в прогоне нет вовсе — pytest умер, не записав отчёт, поэтому сводка его и не назвала (шаг выгрузки при этом зелёный: if-no-files-found: ignore). Логи Actions облачной сессии недоступны — 403.

Почему чинящий именно этот PR. На его голове все обязательные ячейки зелёные, включая все три Windows: 3.12, 3.13, 3.14. Он закрывает гонку создания SQLite-очереди — тот самый флейк, что держал #1506, #1513 и #1528 именно на Windows.

Оговорка честная: доказать, что упало на main именно это, нечем — отчёта нет, логов нет. Но гипотеза единственная проверяемая, а мерж в любом случае даёт новый прогон main и снимает заморозку.

Красные 3.15 — предрелизная ячейка, слияние не держит (#1531, #1537); её содержание — отдельная задача #1534.


Generated by Claude Code

@ArtVsMark
ArtVsMark merged commit 09ff9b8 into main Sep 9, 2026
20 of 23 checks passed
@ArtVsMark
ArtVsMark deleted the agent/queue-creation-race-is-closed branch September 9, 2026 14:35
ArtVsMark added a commit that referenced this pull request Oct 1, 2026
Переход #1420 доведён до конца. В обязательных проверках защиты `main`
остаётся один `ci-complete`; четырнадцать прежних имён — джобы и ячейки
матрицы — убраны из `EXPECTED_CHECKS`.

ЗАЧЕМ. Имя ячейки несёт версию Python и флаг эксперимента, то есть
меняется от ЧУЖОГО календаря. Выход 3.14 из предрелиза добавил сюда три
строки (#1529), выход 3.15 добавил бы ещё три и убрал другие. Каждый
такой сдвиг правится в ДВУХ местах сразу — в дереве и во внешней
настройке репозитория, — а расходятся они молча: PR начинает ждать
проверку, которой больше нет, и ни один прогон об этом не говорит.
Ровно это правило 168 и не велит держать в обязательных.

ЧТО НЕ ПОТЕРЯНО. Защита от переименования ячейки остаётся: состав
выводит `ci_aggregate.expected_checks` из того же `ci.yml`, поэтому
поймает её прогон, а не память. Предрелизные ячейки агрегатор отличает
сам — по суффиксу `, true)`.

ПОЧЕМУ СЕЙЧАС. Порядок перехода менять было нельзя, и он пройден
целиком: агрегатор появился (#1456) → владелец добавил его в
обязательные, ничего не убирая → PR мержились при нём. Последний шаг
доказан на живых случаях, а не обещан: на #1537 и #1540 `ci-complete`
зелёный при красных ячейках 3.15, на #1528 и #1530 — красный, потому
что там красна обязательная (`windows-latest, 3.13` и
`ubuntu-latest, 3.14`). То есть он и пропускает верное, и держит
неверное.

СМЕНИЛСЯ ПРЕДМЕТ ОДНОЙ ПРОВЕРКИ. `check_matrix_names` держала список в
соответствии с матрицей, пока имена перечислялись. Теперь находкой
считается ВОЗВРАЩЕНИЕ матричного имени в обязательные: это шаг назад, к
состоянию, где каждый выпуск CPython правится дважды.

ВЛАДЕЛЬЦУ: те же четырнадцать имён нужно убрать из ruleset, оставив
`ci-complete`. Пока они там, ночная сверка назовёт расхождение —
«обязательных проверок больше заявленного». Это и есть сигнал доделать,
а не поломка гейта.

Closes #1420



Claude-Session: https://claude.ai/code/session_01Wqpt9etxi7Hz6dNyhmYfx1

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/glossary Глоссарий и coverage (glossary/) blocker bug Something isn't working merge-when-green Согласие смержить без автора: авто-мерж включится, как позеленеет

Projects

None yet

2 participants