Repository navigation
fix(glossary): один ответ sqlite о файле — снимок, а не приговор - #1540
Conversation
Конкурентное создание очереди теряло добавки: воркер падал `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
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
|
Ставлю Что на main. Прогон Почему чинящий именно этот PR. На его голове все обязательные ячейки зелёные, включая все три Windows: Оговорка честная: доказать, что упало на main именно это, нечем — отчёта нет, логов нет. Но гипотеза единственная проверяемая, а мерж в любом случае даёт новый прогон Красные Generated by Claude Code |
Переход #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>
Конкурентное создание очереди теряло добавки: воркер падал
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 — реализация.