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 
98     this(Impl* impl) {
99         this.impl = impl;
100     }
101 
102     this(Args...)(auto ref Args args) {
103         // need to use untyped memory, so we don't get a dtor call by the GC.
104         import std.traits : hasIndirections;
105         import std.conv : emplace;
106 
107         static if (hasIndirections!T)
108             auto rawMem = new void[Impl.sizeof];
109         else
110             auto rawMem = new ubyte[Impl.sizeof];
111         impl = (() @trusted => cast(Impl*) rawMem.ptr)();
112         emplace(impl, args);
113         () @trusted { GC.addRoot(impl); }();
114     }
115 
116     this(this) {
117         if (impl) {
118             incrUseCnt(impl);
119         }
120     }
121 
122     ~this() {
123         if (impl) {
124             releaseUseCnt(impl);
125         }
126     }
127 
128     ref inout(T) get() inout {
129         assert(impl, "Invalid refcounted access");
130         return impl.item;
131     }
132 
133     void opAssign(RefCounted other) {
134         swap(impl, other.impl);
135     }
136 
137     void opAssign(T other) {
138         move(other, impl.item);
139     }
140 
141     /// Release the reference.
142     void release() {
143         if (impl) {
144             releaseUseCnt(impl);
145             impl = null;
146         }
147     }
148 
149     /// The number of references.
150     int refCount() @safe pure nothrow const @nogc {
151         if (impl) {
152             return atomicLoad(impl.useCnt);
153         }
154         return 0;
155     }
156 
157     bool empty() @safe pure nothrow const @nogc {
158         return impl is null;
159     }
160 
161     WeakRef!T weakRef() {
162         return WeakRef!T(this);
163     }
164 
165     alias get this;
166 }
167 
168 RefCounted!T refCounted(T)(auto ref T item) {
169     return RefCounted!T(item);
170 }
171 
172 @safe unittest {
173     size_t dtorcalled = 0;
174     struct S {
175         int x;
176         @safe ~this() {
177             if (x)
178                 dtorcalled++;
179         }
180 
181         @disable this(this);
182     }
183 
184     {
185         auto destroyme = S(1).refCounted;
186         auto dm2 = destroyme;
187         auto dm3 = destroyme;
188         assert(destroyme.refCount == 3);
189         assert(dm2.refCount == 3);
190         assert(dm3.refCount == 3);
191     }
192 
193     assert(dtorcalled == 1);
194 }
195 
196 /** `WeakRef` is a smart pointer that holds a non-owning ("weak") reference to
197  * an object that is managed by `RefCounted`. It must be converted to a
198  * `RefCounted` via `asRefCounted()` in order to access the referenced object.
199  *
200  * `WeakRef` models temporary ownership: when an object needs to be accessed
201  * only if it exists, and it may be deleted at any time by someone else,
202  * `WeakRef` is used to track the object, and it is converted to `RefCounted`
203  * to assume temporary ownership. If the original `RefCounted` is destroyed at
204  * this time, the object's lifetime is extended until the temporary
205  * `RefCounted` is destroyed as well.
206  *
207  * Another use for `WeakRef` is to break reference cycles formed by objects
208  * managed by `RefCounted`. if such cycle is orphaned (i.e. there are no
209  * outside shared pointers into the cycle), the `RefCounted` reference counts
210  * cannot reach zero and the memory is leaked. To prevent this, one of the
211  * pointers in the cycle can be made weak.
212  */
213 struct WeakRef(T) {
214     alias Impl = ControlBlock!T;
215     private Impl* impl;
216 
217     this(RefCounted!T r) {
218         incrWeakCnt(r.impl);
219         scope (failure) {
220             releaseWeakCnt(r.impl);
221         }
222         impl = r.impl;
223     }
224 
225     this(ref RefCounted!T r) @safe nothrow {
226         incrWeakCnt(r.impl);
227         impl = r.impl;
228     }
229 
230     this(this) {
231         if (impl) {
232             incrWeakCnt(impl);
233         }
234     }
235 
236     ~this() @safe {
237         if (impl) {
238             releaseWeakCnt(impl);
239         }
240     }
241 
242     void opAssign(WeakRef other) @safe nothrow {
243         swap(impl, other.impl);
244     }
245 
246     RefCounted!T asRefCounted() nothrow {
247         if (impl is null) {
248             return RefCounted!T.init;
249         }
250 
251         auto useCnt = atomicLoad(impl.useCnt);
252         if (useCnt == 0)
253             return RefCounted!T.init;
254 
255         cas(&impl.useCnt, useCnt, useCnt + 1);
256         return RefCounted!T(impl);
257     }
258 
259     /// Release the reference.
260     void release() @safe nothrow @nogc {
261         if (impl) {
262             releaseWeakCnt(impl);
263             impl = null;
264         }
265     }
266 
267     /** If the `WeakRef` reference a `RefCounted`.
268      *
269      * This only mean that `asRefCounted` can be used to try and get the data.
270      * No guarantee that it will succeed.
271      */
272     bool empty() @safe pure nothrow const @nogc {
273         return impl is null;
274     }
275 }
276 
277 /// shall only call the destructor one time.
278 @safe unittest {
279     size_t dtorcalled = 0;
280     struct S {
281         int x;
282         @safe ~this() {
283             if (x)
284                 dtorcalled++;
285         }
286 
287         @disable this(this);
288     }
289 
290     {
291         auto rc1 = S(1).refCounted;
292         assert(rc1.refCount == 1);
293         assert(rc1.impl.weakCnt == 1);
294         auto rc2 = rc1;
295         assert(rc2.refCount == 2);
296         assert(rc2.impl.weakCnt == 1);
297 
298         auto wrc1 = rc1.weakRef;
299         assert(wrc1.impl.useCnt == 2);
300         assert(wrc1.impl.weakCnt == 2);
301     }
302 
303     assert(dtorcalled == 1);
304 }
305 
306 /// shall destroy the object even though there are cycles because they are WeakRef.
307 @safe unittest {
308     size_t dtorcalled = 0;
309     struct S {
310         int x;
311         WeakRef!(typeof(this)) other;
312 
313         @safe ~this() {
314             if (x)
315                 dtorcalled++;
316         }
317 
318         @disable this(this);
319     }
320 
321     {
322         auto rc1 = S(1).refCounted;
323         auto rc2 = S(2).refCounted;
324 
325         rc1.other = rc2.weakRef;
326         rc2.other = rc1.weakRef;
327 
328         assert(rc1.impl.useCnt == 1);
329         assert(rc1.impl.weakCnt == 2);
330         assert(rc2.impl.useCnt == 1);
331         assert(rc2.impl.weakCnt == 2);
332     }
333 
334     assert(dtorcalled == 2);
335 }