| 1 | package activator |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "testing" |
| 7 | "time" |
| 8 | ) |
| 9 | |
| 10 | func TestFenceInitialAdmissionTimeoutHasNoFence(t *testing.T) { |
| 11 | t.Parallel() |
| 12 | a := &Service{} |
| 13 | a.mu.Lock() |
| 14 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) |
| 15 | proof, err := a.FenceAndStop(ctx, "timeout.example.com", 1) |
| 16 | cancel() |
| 17 | if proof != nil || !errors.Is(err, ErrFenceNotInstalled) { |
| 18 | a.mu.Unlock() |
| 19 | t.Fatalf("uninstalled fence reported as installed: proof=%v err=%v", proof != nil, err) |
| 20 | } |
| 21 | if a.fences["timeout.example.com"] != 0 { |
| 22 | a.mu.Unlock() |
| 23 | t.Fatal("fence installed while mutex held") |
| 24 | } |
| 25 | a.mu.Unlock() |
| 26 | if !a.wakeAllowed("timeout.example.com") { |
| 27 | t.Fatal("unexpected admission state") |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | func TestScheduledTokenSlowHostDoesNotBlockSiblingFence(t *testing.T) { |
| 32 | a := &Service{} |
| 33 | entered, release := make(chan struct{}), make(chan struct{}) |
| 34 | done := make(chan error, 1) |
| 35 | go func() { |
| 36 | _, e := a.PrepareScheduledToken("slow.example.com", func() (string, error) { close(entered); <-release; return "token", nil }) |
| 37 | done <- e |
| 38 | }() |
| 39 | <-entered |
| 40 | defer func() { close(release); <-done }() |
| 41 | ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) |
| 42 | defer cancel() |
| 43 | proof, err := a.FenceAndStop(ctx, "sibling.example.com", 1) |
| 44 | if proof != nil || !errors.Is(err, ErrStopUnproven) { |
| 45 | t.Fatalf("sibling fence blocked by unrelated IO: %v", err) |
| 46 | } |
| 47 | callback := make(chan error, 1) |
| 48 | go func() { |
| 49 | _, e := a.PrepareScheduledToken("fast.example.com", func() (string, error) { return "fast", nil }) |
| 50 | callback <- e |
| 51 | }() |
| 52 | select { |
| 53 | case e := <-callback: |
| 54 | if e != nil { |
| 55 | t.Fatal(e) |
| 56 | } |
| 57 | case <-time.After(time.Second): |
| 58 | t.Fatal("sibling token callback blocked") |
| 59 | } |
| 60 | // A separate goroutine detects the former global mutex hold without hanging the test. |
| 61 | result := make(chan bool, 1) |
| 62 | go func() { result <- a.wakeAllowed("third.example.com") }() |
| 63 | select { |
| 64 | case allowed := <-result: |
| 65 | if !allowed { |
| 66 | t.Fatal("unrelated admission refused") |
| 67 | } |
| 68 | case <-time.After(time.Second): |
| 69 | t.Fatal("unrelated admission blocked") |
| 70 | } |
| 71 | } |
| 72 | |