返回 DeepSeek-Reasonix
session_dag_holes.go
根目录 / internal / agent / session_dag_holes.go
1 package agent
2
3 import (
4 "bufio"
5 "context"
6 "errors"
7 "io"
8 "log/slog"
9 "os"
10 )
11
12 // resumePastTornLine continues a replay that stopped at a line it could not
13 // decode. When complete entries follow the bad line it is skipped as a hole,
14 // so a tail another writer appended after a crash-torn line (or an unlocked
15 // shutdown append behind one) stays readable; a bad region that runs to the
16 // end of the file is the torn tail repairSessionDAGTail already handles.
17 func (st *sessionDAGState) resumePastTornLine(ctx context.Context, limits sessionReplayLimits) error {
18 badStart := st.lastGoodEnd
19 if badStart > 0 {
20 badStart++
21 }
22 next, ok, err := nextLineStart(st.path, badStart, limits.maxBytes)
23 if err != nil {
24 return err
25 }
26 if !ok {
27 st.damaged = true
28 return nil
29 }
30 records := st.records
31 if err := st.replayFrom(ctx, next, limits); err != nil {
32 return err
33 }
34 if st.records == records && st.damaged {
35 return nil
36 }
37 st.holes++
38 slog.Warn("session: skipped an unreadable line inside the event log", "path", st.path, "from", badStart, "to", next)
39 return nil
40 }
41
42 // nextLineStart returns the offset just past the first newline at or after
43 // from; ok is false when the rest of the file has none.
44 func nextLineStart(path string, from, maxBytes int64) (int64, bool, error) {
45 f, err := os.Open(path)
46 if err != nil {
47 return 0, false, err
48 }
49 defer f.Close()
50 if _, err := f.Seek(from, io.SeekStart); err != nil {
51 return 0, false, err
52 }
53 r := bufio.NewReader(io.LimitReader(f, maxBytes+1-from))
54 pos := from
55 for {
56 chunk, err := r.ReadSlice('\n')
57 pos += int64(len(chunk))
58 switch {
59 case err == nil:
60 return pos, true, nil
61 case errors.Is(err, bufio.ErrBufferFull):
62 case errors.Is(err, io.EOF):
63 return 0, false, nil
64 default:
65 return 0, false, err
66 }
67 }
68 }
69
69 lines GO