| 1 | package control |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "sync" |
| 7 | "testing" |
| 8 | "time" |
| 9 | |
| 10 | "reasonix/internal/agent" |
| 11 | "reasonix/internal/event" |
| 12 | "reasonix/internal/session" |
| 13 | "reasonix/internal/tool" |
| 14 | ) |
| 15 | |
| 16 | func TestControllerCloseCancelsGoalDriverFlushWait(t *testing.T) { |
| 17 | flushStarted := make(chan struct{}) |
| 18 | releaseFlush := make(chan struct{}) |
| 19 | var once sync.Once |
| 20 | store, err := session.CreateWithOptions(t.TempDir()+"/goal-close-flush", "goal-close-flush", session.OpenOptions{ |
| 21 | Sync: func(*os.File) error { |
| 22 | once.Do(func() { close(flushStarted) }) |
| 23 | <-releaseFlush |
| 24 | return nil |
| 25 | }, |
| 26 | }) |
| 27 | if err != nil { |
| 28 | t.Fatal(err) |
| 29 | } |
| 30 | service, err := session.NewService("desktop", failingFlushPersistence{session: store}) |
| 31 | if err != nil { |
| 32 | t.Fatal(err) |
| 33 | } |
| 34 | t.Cleanup(func() { _ = service.CloseAll(context.Background()) }) |
| 35 | runtime, err := service.Create(t.Context(), session.CreateOptions{SessionID: "goal-close-flush"}) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | replacementBinding, err := service.Bind(runtime) |
| 40 | if err != nil { |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | exec := agent.New(nil, tool.NewRegistry(), agent.NewSession("system"), agent.Options{}, event.Discard) |
| 44 | c := newOwnedTestController(t, Options{Executor: exec, Sink: event.Discard, SessionService: service, SessionRuntime: runtime, ExclusiveSession: true}) |
| 45 | if err := c.SetGoalDurable("close without waiting for a stuck disk"); err != nil { |
| 46 | t.Fatal(err) |
| 47 | } |
| 48 | c.kickGoalDriver() |
| 49 | select { |
| 50 | case <-flushStarted: |
| 51 | case <-time.After(5 * time.Second): |
| 52 | t.Fatal("goal driver did not enter Flush") |
| 53 | } |
| 54 | done := make(chan struct{}) |
| 55 | go func() { |
| 56 | c.ReleaseResources() |
| 57 | close(done) |
| 58 | }() |
| 59 | select { |
| 60 | case <-done: |
| 61 | case <-time.After(time.Second): |
| 62 | close(releaseFlush) |
| 63 | t.Fatal("controller close remained blocked on the goal driver Flush") |
| 64 | } |
| 65 | close(releaseFlush) |
| 66 | if _, err := runtime.Session().Flush(t.Context()); err != nil { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | if err := replacementBinding.Release(t.Context()); err != nil { |
| 70 | t.Fatal(err) |
| 71 | } |
| 72 | } |
| 73 |