gcbarrier_test.go RawHistoryFolder

1package app
2
3import (
4	"context"
5	"errors"
6	"sync"
7	"testing"
8	"time"
9
10	"github.com/bnema/gordon/internal/boundaries/out"
11)
12
13func TestGCBarrierExcludesExclusiveDuringShared(t *testing.T) {
14	barrier := newGCBarrier()
15	ctx := context.Background()
16
17	shared, err := barrier.AcquireShared(ctx)
18	if err != nil {
19		t.Fatalf("AcquireShared: %v", err)
20	}
21
22	// An exclusive lease must wait until the last shared holder is
23	// done, so a prune cannot plan against a half-acquired resource.
24	waitCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
25	defer cancel()
26	if _, err := barrier.AcquireExclusive(waitCtx); !errors.Is(err, context.DeadlineExceeded) {
27		t.Fatalf("exclusive lease during a shared lease returned %v, want deadline exceeded", err)
28	}
29
30	exclusive, err := acquireExclusiveAfter(barrier, shared)
31	if err != nil {
32		t.Fatalf("exclusive lease after release: %v", err)
33	}
34	exclusive.Release()
35}
36
37// acquireExclusiveAfter releases shared, then takes the exclusive lease
38// that must now be grantable.
39func acquireExclusiveAfter(barrier *gcBarrier, shared out.GCLease) (out.GCLease, error) {
40	shared.Release()
41	return barrier.AcquireExclusive(context.Background())
42}
43
44func TestGCBarrierExcludesSharedDuringExclusive(t *testing.T) {
45	barrier := newGCBarrier()
46	ctx := context.Background()
47
48	exclusive, err := barrier.AcquireExclusive(ctx)
49	if err != nil {
50		t.Fatalf("AcquireExclusive: %v", err)
51	}
52	defer exclusive.Release()
53
54	waitCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
55	defer cancel()
56	if _, err := barrier.AcquireShared(waitCtx); !errors.Is(err, context.DeadlineExceeded) {
57		t.Fatalf("shared lease during an exclusive lease returned %v, want deadline exceeded", err)
58	}
59
60	// A second exclusive lease also waits.
61	if _, err := barrier.AcquireExclusive(waitCtx); !errors.Is(err, context.DeadlineExceeded) {
62		t.Fatalf("second exclusive lease returned %v, want deadline exceeded", err)
63	}
64
65	exclusive.Release()
66	shared, err := barrier.AcquireShared(ctx)
67	if err != nil {
68		t.Fatalf("shared lease after release: %v", err)
69	}
70	shared.Release()
71}
72
73func TestGCBarrierCancellation(t *testing.T) {
74	barrier := newGCBarrier()
75	held, err := barrier.AcquireExclusive(context.Background())
76	if err != nil {
77		t.Fatalf("AcquireExclusive: %v", err)
78	}
79	defer held.Release()
80
81	ctx, cancel := context.WithCancel(context.Background())
82	cancel()
83	if _, err := barrier.AcquireShared(ctx); !errors.Is(err, context.Canceled) {
84		t.Fatalf("canceled acquisition returned %v, want context.Canceled", err)
85	}
86
87	// A waiter canceled while blocked returns instead of wedging
88	// shutdown.
89	waitCtx, cancelWait := context.WithCancel(context.Background())
90	done := make(chan error, 1)
91	go func() {
92		_, err := barrier.AcquireShared(waitCtx)
93		done <- err
94	}()
95	cancelWait()
96	select {
97	case err := <-done:
98		if !errors.Is(err, context.Canceled) {
99			t.Fatalf("blocked waiter returned %v, want context.Canceled", err)
100		}
101	case <-time.After(2 * time.Second):
102		t.Fatal("canceled waiter did not return")
103	}
104}
105
106func TestGCBarrierDeterministicHandoff(t *testing.T) {
107	barrier := newGCBarrier()
108	ctx := context.Background()
109
110	// Simulate a workload mutation holding the lease across resource
111	// acquisition and durable publication.
112	shared, err := barrier.AcquireShared(ctx)
113	if err != nil {
114		t.Fatalf("AcquireShared: %v", err)
115	}
116
117	acquired := make(chan struct{})
118	releaseExclusive := make(chan struct{})
119	go func() {
120		exclusive, err := barrier.AcquireExclusive(ctx)
121		if err != nil {
122			close(acquired)
123			return
124		}
125		close(acquired)
126		<-releaseExclusive
127		exclusive.Release()
128	}()
129
130	// The exclusive lease must not be granted while the shared lease is
131	// held, no matter how the goroutine is scheduled.
132	select {
133	case <-acquired:
134		t.Fatal("exclusive lease granted while a shared lease was held")
135	case <-time.After(50 * time.Millisecond):
136	}
137
138	shared.Release()
139	select {
140	case <-acquired:
141	case <-time.After(2 * time.Second):
142		t.Fatal("exclusive lease was never granted after the shared lease was released")
143	}
144
145	// Once the exclusive lease is held, a new shared lease waits.
146	waitCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
147	defer cancel()
148	if _, err := barrier.AcquireShared(waitCtx); !errors.Is(err, context.DeadlineExceeded) {
149		t.Fatalf("shared lease during handoff returned %v, want deadline exceeded", err)
150	}
151
152	close(releaseExclusive)
153	// The barrier must drain back to idle.
154	deadline := time.Now().Add(2 * time.Second)
155	for {
156		lease, err := barrier.AcquireExclusive(context.Background())
157		if err == nil {
158			lease.Release()
159			break
160		}
161		if time.Now().After(deadline) {
162			t.Fatal("barrier never returned to idle")
163		}
164		time.Sleep(time.Millisecond)
165	}
166}
167
168func TestGCBarrierConcurrentSharedHolders(t *testing.T) {
169	barrier := newGCBarrier()
170	ctx := context.Background()
171
172	const holders = 16
173	var wg sync.WaitGroup
174	start := make(chan struct{})
175	for range holders {
176		wg.Add(1)
177		go func() {
178			defer wg.Done()
179			<-start
180			lease, err := barrier.AcquireShared(ctx)
181			if err != nil {
182				t.Errorf("AcquireShared: %v", err)
183				return
184			}
185			time.Sleep(time.Millisecond)
186			lease.Release()
187		}()
188	}
189	close(start)
190
191	// While any shared holder may be active, an exclusive lease must
192	// never be granted concurrently with one.
193	exclusiveDone := make(chan struct{})
194	go func() {
195		defer close(exclusiveDone)
196		for range 200 {
197			lease, err := barrier.AcquireExclusive(ctx)
198			if err != nil {
199				t.Errorf("AcquireExclusive: %v", err)
200				return
201			}
202			barrier.mu.Lock()
203			shared := barrier.shared
204			holding := barrier.exclusive
205			barrier.mu.Unlock()
206			if !holding || shared != 0 {
207				t.Errorf("exclusive lease held with %d shared holders (exclusive=%v)", shared, holding)
208			}
209			lease.Release()
210		}
211	}()
212
213	wg.Wait()
214	<-exclusiveDone
215}
216
217func TestGCBarrierLeaseReleaseIsIdempotent(t *testing.T) {
218	barrier := newGCBarrier()
219	lease, err := barrier.AcquireShared(context.Background())
220	if err != nil {
221		t.Fatalf("AcquireShared: %v", err)
222	}
223	lease.Release()
224	lease.Release()
225
226	barrier.mu.Lock()
227	shared := barrier.shared
228	barrier.mu.Unlock()
229	if shared != 0 {
230		t.Fatalf("shared count after double release = %d, want 0", shared)
231	}
232
233	exclusive, err := barrier.AcquireExclusive(context.Background())
234	if err != nil {
235		t.Fatalf("AcquireExclusive after double release: %v", err)
236	}
237	exclusive.Release()
238}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close