返回 DeepSeek-Reasonix
goal_concurrency_test.go
根目录 / internal / control / goal_concurrency_test.go
1 package control
2
3 import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7 "sync"
8 "testing"
9
10 "reasonix/internal/agent"
11 "reasonix/internal/event"
12 )
13
14 // TestGoalStateWritesAreConcurrencySafe hammers goal-state persistence from many
15 // goroutines (each GoalStrict builds the JSON under c.mu, then writes off-lock via
16 // goalWriteMu) while c.mu-guarded reads run concurrently. Under -race this proves
17 // the new build-under-lock / write-off-lock split has no data race, and that
18 // goalWriteMu keeps the on-disk file from being torn by interleaved writes.
19 func TestGoalStateWritesAreConcurrencySafe(t *testing.T) {
20 dir := t.TempDir()
21 path := filepath.Join(dir, "session.jsonl")
22 sess := agent.NewSession("sys")
23 exec := agent.New(nil, nil, sess, agent.Options{}, event.Discard)
24 c := New(Options{Executor: exec, SessionDir: dir, SessionPath: path, Label: "test"})
25 c.SetGoalWithResearchMode("concurrent goal", GoalResearchOn)
26
27 stop := make(chan struct{})
28 var readers sync.WaitGroup
29 readers.Add(1)
30 go func() {
31 defer readers.Done()
32 for {
33 select {
34 case <-stop:
35 return
36 default:
37 _ = c.Running() // takes c.mu
38 _ = c.RuntimeStatus() // takes c.mu
39 _ = c.Goal() // takes c.mu
40 _ = c.GoalStatus() // takes c.mu
41 }
42 }
43 }()
44
45 var writers sync.WaitGroup
46 for w := range 8 {
47 writers.Add(1)
48 go func(w int) {
49 defer writers.Done()
50 for i := range 10 {
51 c.GoalStrict(i%2 == 0) // build under c.mu, write off-lock
52 }
53 }(w)
54 }
55 writers.Wait()
56 close(stop)
57 readers.Wait()
58
59 // goalWriteMu must have kept the file intact: still valid JSON, goal preserved.
60 data, err := os.ReadFile(goalStatePath(path))
61 if err != nil {
62 t.Fatalf("read goal state: %v", err)
63 }
64 var state goalState
65 if err := json.Unmarshal(data, &state); err != nil {
66 t.Fatalf("goal state file torn by concurrent writes: %v\n%s", err, data)
67 }
68 if state.Goal != "concurrent goal" || state.Status != GoalStatusRunning {
69 t.Fatalf("goal state = %+v, want the active goal preserved", state)
70 }
71 }
72
72 lines GO