| 1 | package session |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "path/filepath" |
| 6 | "testing" |
| 7 | ) |
| 8 | |
| 9 | type gatedCloseHandle struct { |
| 10 | SessionHandle |
| 11 | entered chan struct{} |
| 12 | release chan struct{} |
| 13 | } |
| 14 | |
| 15 | func (h *gatedCloseHandle) Close(ctx context.Context) error { |
| 16 | close(h.entered) |
| 17 | <-h.release |
| 18 | return h.SessionHandle.Close(ctx) |
| 19 | } |
| 20 | |
| 21 | func TestSessionHandleRemainsStableDuringClose(t *testing.T) { |
| 22 | s, err := Open(filepath.Join(t.TempDir(), "session"), "closing") |
| 23 | if err != nil { |
| 24 | t.Fatal(err) |
| 25 | } |
| 26 | handle := &gatedCloseHandle{SessionHandle: s.Handle(), entered: make(chan struct{}), release: make(chan struct{})} |
| 27 | s.binding.handle = handle // Install before publishing the test session to goroutines. |
| 28 | done := make(chan error, 1) |
| 29 | go func() { done <- s.Close(context.Background()) }() |
| 30 | <-handle.entered |
| 31 | got := s.Handle() |
| 32 | state := s.StateSnapshot() |
| 33 | close(handle.release) |
| 34 | if err := <-done; err != nil { |
| 35 | t.Fatal(err) |
| 36 | } |
| 37 | if got != handle || s.Handle() != handle { |
| 38 | t.Fatal("close changed the physical handle visible to concurrent readers") |
| 39 | } |
| 40 | if state.EventSequence != 0 { |
| 41 | t.Fatalf("close changed the accepted sequence: %d", state.EventSequence) |
| 42 | } |
| 43 | if _, err := s.Append(context.Background(), Batch{OperationID: "late", Events: []Event{{Kind: "turn/start"}}}); err == nil { |
| 44 | t.Fatal("retaining the handle reopened write admission") |
| 45 | } |
| 46 | } |
| 47 |