| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- package core
- import (
- "os"
- "os/exec"
- "path/filepath"
- "testing"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // TestCrashRecoveryCommittedOnly 崩溃恢复边界: 子进程提交第一批后, 写入
- // 第二批【不做任何提交】并以 os.Exit 立即死亡 (模拟断电/被杀, 不走红色的
- // Close 兜底提交)。父进程重开数据库验证:
- // - 已提交 (WAL 已落盘) 的第一批必须完整可见 —— 崩溃恢复的职责;
- // - 未提交的第二批必须完全不可见 —— 提交边界的职责;
- // - 文件可正常打开、无结构损坏。
- //
- // 子进程通过环境变量自识别 (go test 子进程测试的标准模式)。
- func TestCrashRecoveryCommittedOnly(t *testing.T) {
- dir := t.TempDir()
- path := filepath.Join(dir, "crash.db")
- // 子进程: 提交 500 行 → 再写 500 行不提交 → 暴毙
- childEnv := append(os.Environ(),
- "TSDB_CRASH_CHILD=1",
- "TSDB_CRASH_DB="+path,
- )
- cmd := exec.Command(os.Args[0], "-test.run=TestCrashChildMain", "-test.v")
- cmd.Env = childEnv
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("子进程: %v\n%s", err, out)
- }
- // 父进程: 重开 (WAL 恢复发生在这里)
- db, err := Open(path)
- if err != nil {
- t.Fatalf("崩溃后重开失败: %v", err)
- }
- defer db.Close()
- tbl, ok := db.FindTable("t")
- if !ok {
- t.Fatalf("表丢失")
- }
- if c := vacCountOf(t, tbl); c != 500 {
- t.Fatalf("崩溃后可见行数 = %d, 想要 500 (已提交批次); 未提交批次必须不可见", c)
- }
- // 已提交行的值正确
- for _, id := range []uint64{1, 250, 500} {
- vals, err := tbl.FindByPK(id)
- if err != nil || !hasColVal(vals, "v") {
- t.Fatalf("已提交行 id=%d 读取失败: %v %+v", id, err, vals)
- }
- }
- // 未提交行必须不存在
- for _, id := range []uint64{501, 750, 1000} {
- if _, err := tbl.FindByPK(id); err == nil {
- t.Fatalf("未提交行 id=%d 在崩溃后可见 (提交边界被破坏)", id)
- }
- }
- // 写入能力未损坏: 新行可插入可回读
- if _, err := tbl.InsertRowWithID(1001, []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(1001)},
- {FieldName: []byte("v"), Value: int64(1)},
- }); err != nil {
- t.Fatalf("崩溃后写入失败: %v", err)
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("commit: %v", err)
- }
- if c := vacCountOf(t, tbl); c != 501 {
- t.Fatalf("写入后行数 = %d, 想要 501", c)
- }
- }
- // TestCrashRecoveryCommittedOnly 的子进程主体 (环境变量 TSDB_CRASH_CHILD=1 时)。
- // 注意: 这个"测试"在子进程里不会跑到 t 的断言, 它只做写入然后 os.Exit。
- func TestCrashChildMain(t *testing.T) {
- if os.Getenv("TSDB_CRASH_CHILD") != "1" {
- return // 正常测试运行: 什么都不做
- }
- path := os.Getenv("TSDB_CRASH_DB")
- db, err := Open(path)
- if err != nil {
- os.Exit(2)
- }
- db.SetAutoCommitDelay(0) // 关掉防抖: 提交完全由显式 Commit 控制
- tbl, err := db.CreateTable("t", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
- })
- if err != nil {
- os.Exit(2)
- }
- if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
- os.Exit(2)
- }
- // 第一批: 显式提交 (WAL + fsync, 必须扛住崩溃)
- for i := 1; i <= 500; i++ {
- if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(i)},
- {FieldName: []byte("v"), Value: int64(i)},
- }); err != nil {
- os.Exit(2)
- }
- }
- if err := db.Commit(); err != nil {
- os.Exit(2)
- }
- // 第二批: 只进 BufferPool, 永不提交
- for i := 501; i <= 1000; i++ {
- if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(i)},
- {FieldName: []byte("v"), Value: int64(i)},
- }); err != nil {
- os.Exit(2)
- }
- }
- // 故意不调 Close / Commit: os.Exit 让脏页随进程蒸发 (模拟断电)
- os.Exit(0)
- }
|