返回 DeepSeek-Reasonix
runtimeset.go
根目录 / internal / extension / runtimeset.go
1 package extension
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "sync"
8 )
9
10 // RuntimeSet tracks live resources bound to one RuntimeSnapshot generation.
11 // It is separate from the immutable snapshot and holds an EffectScope so
12 // activation, receipts, and typed dispose share one owner. CloseIfGeneration
13 // keeps stale cleanup from closing a newer runtime's resources.
14 type RuntimeSet struct {
15 mu sync.Mutex
16 scope *LiveScope
17 // nextCloser sequences auto-generated closer ids for Add.
18 nextCloser int
19 }
20
21 // NewRuntimeSet returns an empty set bound to a snapshot generation.
22 func NewRuntimeSet(generation uint64) *RuntimeSet {
23 return &RuntimeSet{scope: NewEffectScope(generation)}
24 }
25
26 // Scope returns the underlying EffectScope for typed effect registration.
27 func (s *RuntimeSet) Scope() EffectScope {
28 if s == nil || s.scope == nil {
29 return nil
30 }
31 return s.scope
32 }
33
34 // Generation returns the snapshot generation this set is bound to.
35 func (s *RuntimeSet) Generation() uint64 {
36 if s == nil || s.scope == nil {
37 return 0
38 }
39 return s.scope.Generation()
40 }
41
42 // Add registers closers as reversible effects. A closer added to an already
43 // closed set is closed immediately — the alternative (silently leaking it
44 // because the owner went away between activation and registration) is how
45 // sidecar processes outlive their session.
46 func (s *RuntimeSet) Add(closers ...io.Closer) {
47 if s == nil {
48 return
49 }
50 for _, c := range closers {
51 if c == nil {
52 continue
53 }
54 s.mu.Lock()
55 s.nextCloser++
56 id := fmt.Sprintf("closer-%d", s.nextCloser)
57 s.mu.Unlock()
58 _ = s.scope.TrackCloser(id, c)
59 }
60 }
61
62 // Track registers a typed effect on the set's scope.
63 func (s *RuntimeSet) Track(e Effect) error {
64 if s == nil || s.scope == nil {
65 return fmt.Errorf("extension: nil RuntimeSet")
66 }
67 return s.scope.Track(e)
68 }
69
70 // Len returns the number of registered effects that have not yet been disposed.
71 func (s *RuntimeSet) Len() int {
72 if s == nil || s.scope == nil {
73 return 0
74 }
75 s.scope.mu.Lock()
76 defer s.scope.mu.Unlock()
77 return len(s.scope.effects)
78 }
79
80 // Close releases every registered resource, in reverse registration order
81 // (later resources may depend on earlier ones). It is idempotent: concurrent
82 // or repeated calls after the first return nil without re-closing, because
83 // most closers are not safe to invoke twice.
84 func (s *RuntimeSet) Close() error {
85 if s == nil || s.scope == nil {
86 return nil
87 }
88 return s.scope.Dispose(context.Background())
89 }
90
91 // CloseIfGeneration closes only when gen matches; wrong generations leave
92 // resources untouched. Errors collapse to the bool (fire-and-forget cleanup).
93 func (s *RuntimeSet) CloseIfGeneration(gen uint64) bool {
94 if s == nil || s.scope == nil {
95 return false
96 }
97 if gen != s.scope.Generation() {
98 return false
99 }
100 _ = s.Close()
101 return true
102 }
103
104 // Closed reports whether Close has run.
105 func (s *RuntimeSet) Closed() bool {
106 if s == nil || s.scope == nil {
107 return true
108 }
109 return s.scope.Closed()
110 }
111
112 // Receipts returns effect receipts recorded by this generation.
113 func (s *RuntimeSet) Receipts() []EffectReceipt {
114 if s == nil || s.scope == nil {
115 return nil
116 }
117 return s.scope.Receipts()
118 }
119
119 lines GO