- байтовый порядок канонических форм остался тай-брейком только внутри одной доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил меньшее значение, из-за чего step_count терял род и verify:archive был красным - правило перестало быть коммутативным осознанно, поэтому порядок свёртки приведён к журнальному: проход воркера прекращается на отложенной доставке, а свёртка вне порядка журнала пишет WARN - заведены счётчики PointsHeld и PointsErased — удержание полнотой и единственное направление, в котором правило теряет содержание
96 lines
4.4 KiB
Go
96 lines
4.4 KiB
Go
package replay_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"flag"
|
|
"log/slog"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
_ "modernc.org/sqlite" // тот же чистый Go-драйвер, что и у хранилища
|
|
|
|
"git.vakhrushev.me/av/healthlog/internal/fold"
|
|
"git.vakhrushev.me/av/healthlog/internal/replay"
|
|
)
|
|
|
|
// Прогон под удерживаемой блокировкой намеренно не входит в `task test` и
|
|
// `task gate`: `busy_timeout` — пять секунд, повторов транзакции пять, то есть
|
|
// один этот тест стоит около двадцати пяти секунд, а гейт гоняет тесты трижды.
|
|
// Запускается командой `task verify:busy`.
|
|
var runBusy = flag.Bool("healthlog.busy", false,
|
|
"прогнать свёртку под удерживаемой блокировкой базы (около 25 секунд)")
|
|
|
|
// Композиция, ради которой заведён барьер: занятость базы откладывает доставку,
|
|
// проход прекращается на ней, и живая витрина всё равно совпадает с пересборкой.
|
|
//
|
|
// Порознь это уже проверено — барьер в `worker_internal_test.go` на подменённой
|
|
// свёртке, сходимость в `order_test.go` на журнале без отложенной доставки. Но
|
|
// свойство, ради которого изменение существует, — именно композиция: тай-брейк
|
|
// «побеждает пришедшая» сделал содержимое витрины функцией порядка свёртки, и
|
|
// занятость базы это ровно тот случай, который порядок разводит. Здесь она
|
|
// НАСТОЯЩАЯ, а не подменённая: второе соединение держит запись.
|
|
func TestBusyОтложеннаяДоставкаНеРазводитПриёмИПересборку(t *testing.T) {
|
|
if !*runBusy {
|
|
t.Skip("прогон под блокировкой выключен: задайте -healthlog.busy")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
dir := t.TempDir()
|
|
dbPath := filepath.Join(dir, "live.db")
|
|
arch := openArchive(t, filepath.Join(dir, "raw"))
|
|
src := openStore(t, dbPath)
|
|
log := slog.New(slog.DiscardHandler)
|
|
|
|
items := collidingJournal(t, 3)
|
|
for i := range items {
|
|
writeBody(t, arch, src, items[i], bumped(t, items[i].fixture, i))
|
|
}
|
|
|
|
w := replay.NewWorker(src, fold.New(arch, src, 0, log), log)
|
|
|
|
// Второе соединение держит запись, как её держит свёртка широкой доставки:
|
|
// измерено 11 секунд на 16 тысячах объектов, то есть окно реальное.
|
|
holder, err := sql.Open("sqlite", "file:"+dbPath+"?_pragma=busy_timeout(100)&_txlock=immediate")
|
|
if err != nil {
|
|
t.Fatalf("второе соединение: %v", err)
|
|
}
|
|
defer func() { _ = holder.Close() }()
|
|
|
|
tx, err := holder.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
t.Fatalf("удержание записи: %v", err)
|
|
}
|
|
if _, err := tx.Exec(`UPDATE delivery SET points = points`); err != nil {
|
|
t.Fatalf("удержание записи: %v", err)
|
|
}
|
|
|
|
out, err := w.Pass(ctx)
|
|
if err != nil {
|
|
t.Fatalf("проход под блокировкой: %v", err)
|
|
}
|
|
if out.Deferred != 1 {
|
|
t.Fatalf("отложенных %d, ожидалась 1: занятость не дошла до классификации, %+v", out.Deferred, out)
|
|
}
|
|
if out.Folded != 0 {
|
|
t.Fatalf("свёрнуто %d при отложенной ГОЛОВЕ очереди: барьер не сработал, %+v", out.Folded, out)
|
|
}
|
|
|
|
_ = tx.Rollback()
|
|
|
|
// Очередь пошла: те же доставки, тот же порядок журнала.
|
|
out, err = w.Pass(ctx)
|
|
if err != nil {
|
|
t.Fatalf("проход после разблокировки: %v", err)
|
|
}
|
|
if out.Folded != len(items) {
|
|
t.Fatalf("свёрнуто %d из %d: %+v", out.Folded, len(items), out)
|
|
}
|
|
|
|
rep, _ := run(t, ctx, arch, src, filepath.Join(dir, "rebuild.db"))
|
|
if got, want := rep.Fingerprint, fingerprint(t, src); got != want {
|
|
t.Errorf("отложенная занятостью доставка развела приём и пересборку:\n приём %s\n пересборка %s",
|
|
want, got)
|
|
}
|
|
}
|