返回 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 := newOwnedTestController(t, 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.Go(func() {
30 for {
31 select {
32 case <-stop:
33 return
34 default:
35 _ = c.Running() // takes c.mu
36 _ = c.RuntimeStatus() // takes c.mu
37 _ = c.Goal() // takes c.mu
38 _ = c.GoalStatus() // takes c.mu
39 }
40 }
41 })
42
43 var writers sync.WaitGroup
44 for w := range 8 {
45 writers.Add(1)
46 go func(w int) {
47 defer writers.Done()
48 for i := range 10 {
49 c.GoalStrict(i%2 == 0) // build under c.mu, write off-lock
50 }
51 }(w)
52 }
53 writers.Wait()
54 close(stop)
55 readers.Wait()
56
57 // goalWriteMu must have kept the file intact: still valid JSON, goal preserved.
58 data, err := os.ReadFile(goalStatePath(path))
59 if err != nil {
60 t.Fatalf("read goal state: %v", err)
61 }
62 var state goalState
63 if err := json.Unmarshal(data, &state); err != nil {
64 t.Fatalf("goal state file torn by concurrent writes: %v\n%s", err, data)
65 }
66 if state.Goal != "concurrent goal" || state.Status != GoalStatusRunning {
67 t.Fatalf("goal state = %+v, want the active goal preserved", state)
68 }
69 }
70
70 lines GO