返回 DeepSeek-Reasonix
history_log_scan.go
根目录 / internal / session / history_log_scan.go
1 package session
2
3 import (
4 "context"
5 "database/sql"
6 "io"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "reasonix/internal/sessioncontent"
12 )
13
14 // historyLogProgress couples coverage to the end of the same validated batch.
15 // Physical EOF may include an uncommitted tail and must never be a checkpoint.
16 type historyLogProgress struct {
17 end int64
18 sequence uint64
19 modTimeNS int64
20 }
21
22 func (m historyIndexMetadata) canIncrement(sessionID string, revision logRevision, generation string) bool {
23 return m.sessionID == sessionID && m.storageRevision == StorageRevision && m.projection == historyIndexVersion &&
24 m.logSize >= 0 && m.logSize < revision.Size && strings.HasPrefix(m.generation, strings.TrimSuffix(generation, ":0")+":")
25 }
26
27 func configureHistoryRebuild(ctx context.Context, db *sql.DB) error {
28 // Only applied to an unpublished, disposable replacement database.
29 for _, pragma := range []string{
30 `PRAGMA journal_mode=OFF`, `PRAGMA synchronous=OFF`, `PRAGMA locking_mode=EXCLUSIVE`,
31 `PRAGMA cache_size=-8192`, `PRAGMA temp_store=FILE`,
32 } {
33 if _, err := db.ExecContext(ctx, pragma); err != nil {
34 return err
35 }
36 }
37 return nil
38 }
39
40 func scanHistoryLog(ctx context.Context, log *os.File, start int64, next uint64, limit int64, content *sessioncontent.Store, visit func(Commit) bool) (historyLogProgress, error) {
41 progress := historyLogProgress{end: start, sequence: next - 1}
42 info, err := log.Stat()
43 if err != nil {
44 return progress, err
45 }
46 progress.modTimeNS = info.ModTime().UnixNano()
47 // A finite snapshot prevents continuous appends from extending the scan.
48 reader := io.NewSectionReader(log, 0, min(limit, info.Size()))
49 err = scanV4CommitFileRefs(ctx, reader, start, next, content, nil, func(_ int64, commit Commit) bool {
50 if !visit(commit) {
51 return false
52 }
53 progress.end, _ = reader.Seek(0, io.SeekCurrent) // SectionReader position cannot fail.
54 progress.sequence = commit.LastSequence()
55 return true
56 })
57 return progress, err
58 }
59
60 // Replacing a session while it is scanned must not publish an index for the
61 // old file under the replacement's identity. Appends preserve both identities.
62 func validateHistoryLog(ctx context.Context, dir string, log *os.File, generation string) error {
63 if err := ctx.Err(); err != nil {
64 return err
65 }
66 current, err := historyProjectionGeneration(dir, 0)
67 if err != nil {
68 return err
69 }
70 if current != generation {
71 return ErrStaleGeneration
72 }
73 manifest, err := readStoredManifest(filepath.Join(dir, "manifest.json"))
74 if err != nil {
75 return err
76 }
77 currentFile, err := os.Stat(logPathForManifest(dir, manifest))
78 if err != nil {
79 return err
80 }
81 openedFile, err := log.Stat()
82 if err != nil {
83 return err
84 }
85 if !os.SameFile(currentFile, openedFile) {
86 return ErrStaleGeneration
87 }
88 return nil
89 }
90
90 lines GO