gcbarrier.go RawHistoryFolder
1package app
2
3import (
4 "context"
5 "sync"
6
7 "github.com/bnema/gordon/internal/boundaries/out"
8)
9
10// gcBarrier is the process-wide GC barrier.
11//
12// Shared leases cover the window in which a deploy, start, restart,
13// remove, recovery, or restore path selects, acquires, and durably
14// publishes the protection of a runtime resource. One exclusive lease
15// covers prune's protection snapshot, planning, and exact deletion.
16//
17// Lock order is fixed: GC barrier → per-app coordinator → registry
18// mutation lock → short bbolt transaction. Callers therefore take a
19// shared lease BEFORE the per-app coordinator, never the other way
20// round.
21type gcBarrier struct {
22 mu sync.Mutex
23 shared int
24 exclusive bool
25 // changed is closed and replaced whenever a lease is released, so
26 // waiters re-check availability.
27 changed chan struct{}
28}
29
30// newGCBarrier creates an idle process-wide barrier.
31func newGCBarrier() *gcBarrier {
32 return &gcBarrier{changed: make(chan struct{})}
33}
34
35// AcquireShared implements out.GCBarrier.
36func (b *gcBarrier) AcquireShared(ctx context.Context) (out.GCLease, error) {
37 return b.acquire(ctx, false)
38}
39
40// AcquireExclusive implements out.GCBarrier.
41func (b *gcBarrier) AcquireExclusive(ctx context.Context) (out.GCLease, error) {
42 return b.acquire(ctx, true)
43}
44
45func (b *gcBarrier) acquire(ctx context.Context, exclusive bool) (out.GCLease, error) {
46 for {
47 if err := ctx.Err(); err != nil {
48 return nil, err
49 }
50 b.mu.Lock()
51 if b.available(exclusive) {
52 if exclusive {
53 b.exclusive = true
54 } else {
55 b.shared++
56 }
57 b.mu.Unlock()
58 return &gcLease{barrier: b, exclusive: exclusive}, nil
59 }
60 wait := b.changed
61 b.mu.Unlock()
62
63 select {
64 case <-ctx.Done():
65 return nil, ctx.Err()
66 case <-wait:
67 }
68 }
69}
70
71// available reports whether a lease of the requested kind can be taken
72// without violating the exclusion rules. The caller holds mu.
73func (b *gcBarrier) available(exclusive bool) bool {
74 if exclusive {
75 return !b.exclusive && b.shared == 0
76 }
77 return !b.exclusive
78}
79
80// release drops one held lease and wakes waiters. The caller must hold
81// no lock.
82func (b *gcBarrier) release(exclusive bool) {
83 b.mu.Lock()
84 defer b.mu.Unlock()
85 if exclusive {
86 b.exclusive = false
87 } else if b.shared > 0 {
88 b.shared--
89 }
90 close(b.changed)
91 b.changed = make(chan struct{})
92}
93
94// gcLease is one held barrier lease. Release is idempotent.
95type gcLease struct {
96 barrier *gcBarrier
97 exclusive bool
98 once sync.Once
99}
100
101// Release implements out.GCLease.
102func (l *gcLease) Release() {
103 if l == nil || l.barrier == nil {
104 return
105 }
106 l.once.Do(func() { l.barrier.release(l.exclusive) })
107}
108
109var _ out.GCBarrier = (*gcBarrier)(nil)