fix: 修复写队列 busy 恢复、关闭安全、备份与配置构建问题
This commit is contained in:
@@ -5,6 +5,9 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -148,6 +151,119 @@ func TestWriteQueueNoBacklogAt200PerSec(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteQueueRecoversReadyAfterBusy(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := t.TempDir()
|
||||
db, err := Open(dir, "FULL")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
db.Queue.markBusy(errors.New("injected busy"))
|
||||
if db.Queue.IsReady() {
|
||||
t.Fatal("expected not ready after markBusy")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err := db.Queue.Do(ctx, func(tx *sql.Tx) error {
|
||||
_, e := tx.Exec(
|
||||
`INSERT INTO settings(key, value, updated_at) VALUES(?, ?, ?)`,
|
||||
"after_busy", "1", time.Now().UnixMilli(),
|
||||
)
|
||||
return e
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !db.Queue.IsReady() {
|
||||
t.Fatalf("expected ready after successful Do, last=%v", db.Queue.LastWriteError())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteQueueCloseConcurrentDoNoPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := t.TempDir()
|
||||
db, err := Open(dir, "FULL")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
ctx := context.Background()
|
||||
const n = 1000
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_ = db.Queue.Do(ctx, func(tx *sql.Tx) error {
|
||||
_, e := tx.Exec(
|
||||
`INSERT INTO settings(key, value, updated_at) VALUES(?, ?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,
|
||||
fmt.Sprintf("close_%d", i%50), "1", time.Now().UnixMilli(),
|
||||
)
|
||||
return e
|
||||
})
|
||||
}()
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
if err := db.Queue.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
err = db.Queue.Do(ctx, func(tx *sql.Tx) error { return nil })
|
||||
if !errors.Is(err, ErrQueueClosed) {
|
||||
t.Fatalf("want ErrQueueClosed, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueCheckpointTruncatesWAL(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := t.TempDir()
|
||||
db, err := Open(dir, "FULL")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
ctx := context.Background()
|
||||
payload := strings.Repeat("x", 4096)
|
||||
for i := 0; i < 300; i++ {
|
||||
i := i
|
||||
if err := db.Queue.Do(ctx, func(tx *sql.Tx) error {
|
||||
_, e := tx.Exec(
|
||||
`INSERT INTO settings(key, value, updated_at) VALUES(?, ?, ?)`,
|
||||
fmt.Sprintf("wal_%d", i), payload, time.Now().UnixMilli(),
|
||||
)
|
||||
return e
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
walPath := filepath.Join(dir, DBFileName+"-wal")
|
||||
before, statErr := os.Stat(walPath)
|
||||
if err := db.Checkpoint(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if statErr != nil {
|
||||
return
|
||||
}
|
||||
after, err := os.Stat(walPath)
|
||||
if err != nil {
|
||||
// TRUNCATE 后 WAL 可能被删掉,视为回落成功。
|
||||
if os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
if before.Size() > 0 && after.Size() >= before.Size() {
|
||||
t.Fatalf("WAL did not shrink: before=%d after=%d", before.Size(), after.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWriteQueue200PerSec(b *testing.B) {
|
||||
dir := b.TempDir()
|
||||
db, err := Open(dir, "FULL")
|
||||
|
||||
Reference in New Issue
Block a user