1 /**
2  * [Source](https://raw.githubusercontent.com/schveiguy/iopipe/makesafe/source/iopipe/refc.d).
3  *
4  * Reference counting using the GC.
5  *
6  * The RefCounted struct simply stores the item in a GC block, and also adds a
7  * root to that block. Once all known references to the block are removed
8  * (tracked by a reference count in the block), then the block is removed, and
9  * the destructor run. Since it's a root, it can run the full destructor of the
10  * data underneath, without worrying about GC data being collected underneath
11  * it.
12  *
13  * This depends on the block not being involved in a cycle, which should be
14  * fine for iopipes.
15  *
16  * Note that atomics are used for the reference count because the GC can
17  * destroy things in other threads.
18  */
19 module my.gc.refc;
20 
21 import core.atomic : atomicOp, atomicLoad, atomicStore, cas;
22 import core.memory : GC;
23 import std.algorithm : move, swap;
24 
25 /**
26  * The "use count" is the number of shared_ptr instances pointing to the
27  * object.
28  * The "weak count" is the number of weak_ptr instances pointing to the object,
29  * plus one if the "use count" is still > 0.
30  */
31 private struct ControlBlock(T) {
32     T item;
33     /// Number of RefCounted instances.
34     shared int useCnt = 1;
35     /// Number of weak references. +1 if useCnt isn't zero.
36     shared int weakCnt = 1;
37 
38     this(ref T item_) {
39         item = move(item_);
40     }
41 
42     this(Args...)(auto ref Args args) {
43         item = T(args);
44     }
45 }
46 
47 private void incrUseCnt(T)(ref T cb) nothrow {
48     cb.useCnt.atomicOp!"+="(1);
49 }
50 
51 private void releaseUseCnt(T)(ref T cb) {
52     assert(cb.useCnt >= 0, "Invalid count detected");
53 
54     if (cb.useCnt.atomicOp!"-="(1) == 0) {
55         destroy(cb.item);
56         releaseWeakCnt(cb);
57     }
58 }
59 
60 private void incrWeakCnt(T)(ref T cb) nothrow {
61     cb.weakCnt.atomicOp!"+="(1);
62 }
63 
64 private void releaseWeakCnt(T)(ref T cb) @trusted {
65     assert(cb.weakCnt >= 0, "Invalid count detected");
66 
67     if (cb.weakCnt.atomicOp!"-="(1) == 0) {
68         GC.removeRoot(cb);
69     }
70 }
71 
72 /** `RefCounted` is a smart pointer that retains shared ownership of an object
73  * through a pointer. Several `RefCounted` objects may own the same object. The
74  * object is destroyed and its memory deallocated when either of the following
75  * happens:
76  *
77  *  * the last remaining `RefCounted` owning the object is destroyed;
78  *  * the last remaining `RefCounted` owning the object is assigned another
79  *    pointer via `opAssign` or `release()`.
80  *
81  * The object is destroyed using the objects destructor.
82  *
83  * A `RefCounted` may also own no objects, in which case it is called empty and
84  * `isNull()` returns true.
85  *
86  * All member functions can be called by multiple threads on different
87  * instances of shared_ptr without additional synchronization even if these
88  * instances are copies and share ownership of the same object. If multiple
89  * threads of execution access the same shared_ptr without synchronization and
90  * any of those accesses uses a non-const member function of shared_ptr then a
91  * data race will occur; the shared_ptr overloads of atomic functions can be
92  * used to prevent the data race.
93  */
94 struct RefCounted(T) {
95     alias Impl = ControlBlock!T;
96     private Impl* impl;
97     private T* item;
98 
99     this(Impl* impl) {
100         this.impl = impl;
101         setLocalItem;
102     }
103 
104     this(Args...)(auto ref Args args) {
105         import std.conv : emplace;
106 
107         impl = alloc();
108         () @trusted { emplace(impl, args); GC.addRoot(impl); }();
109         setLocalItem;
110     }
111 
112     this(this) {
113         if (impl) {
114             incrUseCnt(impl);
115         }
116     }
117 
118     ~this() {
119         if (impl) {
120             releaseUseCnt(impl);
121         }
122     }
123 
124     /// Set impl to an allocated block of data. It is uninitialized.
125     private static Impl* alloc() @trusted {
126         // need to use untyped memory, so we don't get a dtor call by the GC.
127         import std.traits : hasIndirections;
128 
129         static if (hasIndirections!T)
130             auto rawMem = new void[Impl.sizeof];
131         else
132             auto rawMem = new ubyte[Impl.sizeof];
133         return (() @trusted => cast(Impl*) rawMem.ptr)();
134     }
135 
136     private void setLocalItem() @trusted {
137         if (impl)
138             item = &impl.item;
139     }
140 
141     ref inout(T) get() inout {
142         assert(impl, "Invalid refcounted access");
143         return *item;
144     }
145 
146     void opAssign(RefCounted other) {
147         swap(impl, other.impl);
148         setLocalItem;
149     }
150 
151     void opAssign(T other) {
152         import std.conv : emplace;
153 
154         if (empty) {
155             impl = alloc;
156             () @trusted { emplace(impl, other); GC.addRoot(impl); }();
157         } else {
158             move(other, impl.item);
159         }
160         setLocalItem;
161     }
162 
163     /// Release the reference.
164     void release() {
165         if (impl) {
166             releaseUseCnt(impl);
167             impl = null;
168         }
169     }
170 
171     /// The number of references.
172     int refCount() @safe pure nothrow const @nogc {
173         if (impl) {
174             return atomicLoad(impl.useCnt);
175         }
176         return 0;
177     }
178 
179     bool empty() @safe pure nothrow const @nogc {
180         return impl is null;
181     }
182 
183     WeakRef!T weakRef() {
184         return WeakRef!T(this);
185     }
186 
187     alias get this;
188 }
189 
190 RefCounted!T refCounted(T)(auto ref T item) {
191     return RefCounted!T(item);
192 }
193 
194 @safe unittest {
195     size_t dtorcalled = 0;
196     struct S {
197         int x;
198         @safe ~this() {
199             if (x)
200                 dtorcalled++;
201         }
202 
203         @disable this(this);
204     }
205 
206     {
207         auto destroyme = S(1).refCounted;
208         auto dm2 = destroyme;
209         auto dm3 = destroyme;
210         assert(destroyme.refCount == 3);
211         assert(dm2.refCount == 3);
212         assert(dm3.refCount == 3);
213     }
214 
215     assert(dtorcalled == 1);
216 }
217 
218 /** `WeakRef` is a smart pointer that holds a non-owning ("weak") reference to
219  * an object that is managed by `RefCounted`. It must be converted to a
220  * `RefCounted` via `asRefCounted()` in order to access the referenced object.
221  *
222  * `WeakRef` models temporary ownership: when an object needs to be accessed
223  * only if it exists, and it may be deleted at any time by someone else,
224  * `WeakRef` is used to track the object, and it is converted to `RefCounted`
225  * to assume temporary ownership. If the original `RefCounted` is destroyed at
226  * this time, the object's lifetime is extended until the temporary
227  * `RefCounted` is destroyed as well.
228  *
229  * Another use for `WeakRef` is to break reference cycles formed by objects
230  * managed by `RefCounted`. if such cycle is orphaned (i.e. there are no
231  * outside shared pointers into the cycle), the `RefCounted` reference counts
232  * cannot reach zero and the memory is leaked. To prevent this, one of the
233  * pointers in the cycle can be made weak.
234  */
235 struct WeakRef(T) {
236     alias Impl = ControlBlock!T;
237     private Impl* impl;
238     private T* item;
239 
240     this(RefCounted!T r) {
241         incrWeakCnt(r.impl);
242         scope (failure) {
243             releaseWeakCnt(r.impl);
244         }
245         impl = r.impl;
246     }
247 
248     this(ref RefCounted!T r) @safe nothrow {
249         incrWeakCnt(r.impl);
250         impl = r.impl;
251         setLocalItem;
252     }
253 
254     this(this) {
255         if (impl) {
256             incrWeakCnt(impl);
257         }
258     }
259 
260     ~this() @safe {
261         if (impl) {
262             releaseWeakCnt(impl);
263         }
264     }
265 
266     private void setLocalItem() @trusted {
267         if (impl)
268             item = &impl.item;
269     }
270 
271     void opAssign(WeakRef other) @safe nothrow {
272         swap(impl, other.impl);
273         setLocalItem;
274     }
275 
276     RefCounted!T asRefCounted() nothrow {
277         if (impl is null) {
278             return RefCounted!T.init;
279         }
280 
281         auto useCnt = atomicLoad(impl.useCnt);
282         if (useCnt == 0)
283             return RefCounted!T.init;
284 
285         cas(&impl.useCnt, useCnt, useCnt + 1);
286         return RefCounted!T(impl);
287     }
288 
289     /// Release the reference.
290     void release() @safe nothrow @nogc {
291         if (impl) {
292             releaseWeakCnt(impl);
293             impl = null;
294         }
295     }
296 
297     /** If the `WeakRef` reference a `RefCounted`.
298      *
299      * This only mean that `asRefCounted` can be used to try and get the data.
300      * No guarantee that it will succeed.
301      */
302     bool empty() @safe pure nothrow const @nogc {
303         return impl is null;
304     }
305 }
306 
307 /// shall only call the destructor one time.
308 @safe unittest {
309     size_t dtorcalled = 0;
310     struct S {
311         int x;
312         @safe ~this() {
313             if (x)
314                 dtorcalled++;
315         }
316 
317         @disable this(this);
318     }
319 
320     {
321         auto rc1 = S(1).refCounted;
322         assert(rc1.refCount == 1);
323         assert(rc1.impl.weakCnt == 1);
324         auto rc2 = rc1;
325         assert(rc2.refCount == 2);
326         assert(rc2.impl.weakCnt == 1);
327 
328         auto wrc1 = rc1.weakRef;
329         assert(wrc1.impl.useCnt == 2);
330         assert(wrc1.impl.weakCnt == 2);
331     }
332 
333     assert(dtorcalled == 1);
334 }
335 
336 /// shall destroy the object even though there are cycles because they are WeakRef.
337 @safe unittest {
338     size_t dtorcalled = 0;
339     struct S {
340         int x;
341         WeakRef!(typeof(this)) other;
342 
343         @safe ~this() {
344             if (x)
345                 dtorcalled++;
346         }
347 
348         @disable this(this);
349     }
350 
351     {
352         auto rc1 = S(1).refCounted;
353         auto rc2 = S(2).refCounted;
354 
355         rc1.other = rc2.weakRef;
356         rc2.other = rc1.weakRef;
357 
358         assert(rc1.impl.useCnt == 1);
359         assert(rc1.impl.weakCnt == 2);
360         assert(rc2.impl.useCnt == 1);
361         assert(rc2.impl.weakCnt == 2);
362     }
363 
364     assert(dtorcalled == 2);
365 }