1 package buffer
2 3 import (
4 "context"
5 "fmt"
6 7 "gvisor.dev/gvisor/pkg/atomicbitops"
8 "gvisor.dev/gvisor/pkg/refs"
9 )
10 11 // enableLogging indicates whether reference-related events should be logged (with
12 // stack traces). This is false by default and should only be set to true for
13 // debugging purposes, as it can generate an extremely large amount of output
14 // and drastically degrade performance.
15 const chunkenableLogging = false
16 17 // obj is used to customize logging. Note that we use a pointer to T so that
18 // we do not copy the entire object when passed as a format parameter.
19 var chunkobj *chunk
20 21 // Refs implements refs.RefCounter. It keeps a reference count using atomic
22 // operations and calls the destructor when the count reaches zero.
23 //
24 // NOTE: Do not introduce additional fields to the Refs struct. It is used by
25 // many filesystem objects, and we want to keep it as small as possible (i.e.,
26 // the same size as using an int64 directly) to avoid taking up extra cache
27 // space. In general, this template should not be extended at the cost of
28 // performance. If it does not offer enough flexibility for a particular object
29 // (example: b/187877947), we should implement the RefCounter/CheckedObject
30 // interfaces manually.
31 //
32 // +stateify savable
33 type chunkRefs struct {
34 // refCount is composed of two fields:
35 //
36 // [32-bit speculative references]:[32-bit real references]
37 //
38 // Speculative references are used for TryIncRef, to avoid a CompareAndSwap
39 // loop. See IncRef, DecRef and TryIncRef for details of how these fields are
40 // used.
41 refCount atomicbitops.Int64
42 }
43 44 // InitRefs initializes r with one reference and, if enabled, activates leak
45 // checking.
46 func (r *chunkRefs) InitRefs() {
47 48 r.refCount.RacyStore(1)
49 refs.Register(r)
50 }
51 52 // RefType implements refs.CheckedObject.RefType.
53 func (r *chunkRefs) RefType() string {
54 return fmt.Sprintf("%T", chunkobj)[1:]
55 }
56 57 // LeakMessage implements refs.CheckedObject.LeakMessage.
58 func (r *chunkRefs) LeakMessage() string {
59 return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
60 }
61 62 // LogRefs implements refs.CheckedObject.LogRefs.
63 func (r *chunkRefs) LogRefs() bool {
64 return chunkenableLogging
65 }
66 67 // ReadRefs returns the current number of references. The returned count is
68 // inherently racy and is unsafe to use without external synchronization.
69 func (r *chunkRefs) ReadRefs() int64 {
70 return r.refCount.Load()
71 }
72 73 // IncRef implements refs.RefCounter.IncRef.
74 //
75 //go:nosplit
76 func (r *chunkRefs) IncRef() {
77 v := r.refCount.Add(1)
78 if chunkenableLogging {
79 refs.LogIncRef(r, v)
80 }
81 if v <= 1 {
82 panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
83 }
84 }
85 86 // TryIncRef implements refs.TryRefCounter.TryIncRef.
87 //
88 // To do this safely without a loop, a speculative reference is first acquired
89 // on the object. This allows multiple concurrent TryIncRef calls to distinguish
90 // other TryIncRef calls from genuine references held.
91 //
92 //go:nosplit
93 func (r *chunkRefs) TryIncRef() bool {
94 const speculativeRef = 1 << 32
95 if v := r.refCount.Add(speculativeRef); int32(v) == 0 {
96 97 r.refCount.Add(-speculativeRef)
98 return false
99 }
100 101 v := r.refCount.Add(-speculativeRef + 1)
102 if chunkenableLogging {
103 refs.LogTryIncRef(r, v)
104 }
105 return true
106 }
107 108 // DecRef implements refs.RefCounter.DecRef.
109 //
110 // Note that speculative references are counted here. Since they were added
111 // prior to real references reaching zero, they will successfully convert to
112 // real references. In other words, we see speculative references only in the
113 // following case:
114 //
115 // A: TryIncRef [speculative increase => sees non-negative references]
116 // B: DecRef [real decrease]
117 // A: TryIncRef [transform speculative to real]
118 //
119 //go:nosplit
120 func (r *chunkRefs) DecRef(destroy func()) {
121 v := r.refCount.Add(-1)
122 if chunkenableLogging {
123 refs.LogDecRef(r, v)
124 }
125 switch {
126 case v < 0:
127 panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
128 129 case v == 0:
130 refs.Unregister(r)
131 132 if destroy != nil {
133 destroy()
134 }
135 }
136 }
137 138 func (r *chunkRefs) afterLoad(context.Context) {
139 if r.ReadRefs() > 0 {
140 refs.Register(r)
141 }
142 }
143