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 in (cb.useCnt >= 0, "Invalid count detected") {
53     if (cb.useCnt.atomicOp!"-="(1) == 0) {
54         if (!GC.inFinalizer)
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 in (cb.weakCnt >= 0, "Invalid count detected") {
66     if (cb.weakCnt.atomicOp!"-="(1) == 0) {
67         GC.removeRoot(cb);
68     }
69 }
70 
71 /** `RefCounted` is a smart pointer that retains shared ownership of an object
72  * through a pointer. Several `RefCounted` objects may own the same object. The
73  * object is destroyed and its memory deallocated when either of the following
74  * happens:
75  *
76  *  * the last remaining `RefCounted` owning the object is destroyed;
77  *  * the last remaining `RefCounted` owning the object is assigned another
78  *    pointer via `opAssign` or `release()`.
79  *
80  * The object is destroyed using the objects destructor.
81  *
82  * A `RefCounted` may also own no objects, in which case it is called empty and
83  * `isNull()` returns true.
84  *
85  * All member functions can be called by multiple threads on different
86  * instances of shared_ptr without additional synchronization even if these
87  * instances are copies and share ownership of the same object. If multiple
88  * threads of execution access the same shared_ptr without synchronization and
89  * any of those accesses uses a non-const member function of shared_ptr then a
90  * data race will occur; the shared_ptr overloads of atomic functions can be
91  * used to prevent the data race.
92  */
93 struct RefCounted(T) {
94     import std.conv : emplace;
95 
96     alias Impl = ControlBlock!T;
97     private Impl* impl;
98 
99     this(Impl* impl) {
100         this.impl = impl;
101     }
102 
103     this(Args...)(auto ref Args args) {
104         impl = alloc();
105         () @trusted {
106             scope (failure)
107                 GC.removeRoot(impl);
108             emplace(impl, args);
109         }();
110     }
111 
112     this(this) {
113         if (impl)
114             incrUseCnt(impl);
115     }
116 
117     ~this() {
118         if (impl)
119             releaseUseCnt(impl);
120         impl = null;
121     }
122 
123     /// Set impl to an allocated block of data. It is uninitialized.
124     private static Impl* alloc() @trusted {
125         // need to use untyped memory, so we don't get a dtor call by the GC.
126         import std.traits : hasIndirections;
127 
128         static if (hasIndirections!T) {
129             auto rawMem = new void[Impl.sizeof];
130             GC.addRoot(rawMem.ptr);
131         } else {
132             auto rawMem = new ubyte[Impl.sizeof];
133         }
134 
135         auto rval = cast(Impl*) rawMem.ptr;
136         rval.useCnt = 1;
137         rval.weakCnt = 1;
138         return rval;
139     }
140 
141     private inout(T*) item() inout @trusted
142     in (impl !is null, "not initialized") {
143         return cast(inout(T*)) impl;
144     }
145 
146     /// Returns: pointer to the item or null.
147     inout(T*) ptr() inout scope return  {
148         return item;
149     }
150 
151     ref inout(T) get() inout
152     in (item !is null, "Invalid refcounted access") {
153         return *item;
154     }
155 
156     // creates forwarding problem but is convenient.
157     //alias get this;
158 
159     size_t toHash() @safe pure nothrow const @nogc scope {
160         return cast(size_t) impl;
161     }
162 
163     void opAssign(RefCounted other) {
164         swap(impl, other.impl);
165     }
166 
167     void opAssign(T other) {
168         if (empty)
169             impl = alloc;
170         move(other, impl.item);
171     }
172 
173     /// Release the reference.
174     void release() {
175         if (impl) {
176             releaseUseCnt(impl);
177             impl = null;
178         }
179     }
180 
181     /// The number of references.
182     int refCount() @safe pure nothrow const @nogc {
183         if (impl) {
184             return atomicLoad(impl.useCnt);
185         }
186         return 0;
187     }
188 
189     bool empty() @safe pure nothrow const @nogc {
190         return impl is null;
191     }
192 
193     T opCast(T : bool)() @safe pure nothrow const @nogc {
194         return !empty;
195     }
196 
197     WeakRef!T weakRef() {
198         return WeakRef!T(this);
199     }
200 }
201 
202 RefCounted!T refCounted(T)(auto ref T item) {
203     return RefCounted!T(item);
204 }
205 
206 @("shall call the destructor when the last ref is destroyed")
207 @safe unittest {
208     size_t dtorcalled = 0;
209     struct S {
210         int x;
211         @safe ~this() {
212             if (x)
213                 dtorcalled++;
214         }
215 
216         @disable this(this);
217     }
218 
219     {
220         auto destroyme = S(1).refCounted;
221         auto dm2 = destroyme;
222         auto dm3 = destroyme;
223         assert(destroyme.refCount == 3);
224         assert(dm2.refCount == 3);
225         assert(dm3.refCount == 3);
226     }
227 
228     assert(dtorcalled == 1);
229 }
230 
231 /** `WeakRef` is a smart pointer that holds a non-owning ("weak") reference to
232  * an object that is managed by `RefCounted`. It must be converted to a
233  * `RefCounted` via `asRefCounted()` in order to access the referenced object.
234  *
235  * `WeakRef` models temporary ownership: when an object needs to be accessed
236  * only if it exists, and it may be deleted at any time by someone else,
237  * `WeakRef` is used to track the object, and it is converted to `RefCounted`
238  * to assume temporary ownership. If the original `RefCounted` is destroyed at
239  * this time, the object's lifetime is extended until the temporary
240  * `RefCounted` is destroyed as well.
241  *
242  * Another use for `WeakRef` is to break reference cycles formed by objects
243  * managed by `RefCounted`. if such cycle is orphaned (i.e. there are no
244  * outside shared pointers into the cycle), the `RefCounted` reference counts
245  * cannot reach zero and the memory is leaked. To prevent this, one of the
246  * pointers in the cycle can be made weak.
247  */
248 struct WeakRef(T) {
249     alias Impl = ControlBlock!T;
250     private Impl* impl;
251 
252     this(RefCounted!(T) r) {
253         if (r.empty)
254             return;
255 
256         incrWeakCnt(r.impl);
257         impl = r.impl;
258     }
259 
260     this(ref RefCounted!(T) r) {
261         if (r.empty)
262             return;
263 
264         incrWeakCnt(r.impl);
265         impl = r.impl;
266     }
267 
268     this(this) {
269         if (impl)
270             incrWeakCnt(impl);
271     }
272 
273     ~this() @safe {
274         if (impl)
275             releaseWeakCnt(impl);
276         impl = null;
277     }
278 
279     size_t toHash() @safe pure nothrow const @nogc scope {
280         return cast(size_t) impl;
281     }
282 
283     void opAssign(WeakRef other) @safe nothrow {
284         swap(impl, other.impl);
285     }
286 
287     RefCounted!(T) asRefCounted() nothrow {
288         if (impl is null) {
289             return typeof(return).init;
290         }
291 
292         auto useCnt = atomicLoad(impl.useCnt);
293         if (useCnt == 0)
294             return typeof(return).init;
295 
296         cas(&impl.useCnt, useCnt, useCnt + 1);
297         return typeof(return)(impl);
298     }
299 
300     /// Release the reference.
301     void release() @safe nothrow @nogc {
302         if (impl) {
303             releaseWeakCnt(impl);
304             impl = null;
305         }
306     }
307 
308     /** If the `WeakRef` reference a `RefCounted`.
309      *
310      * This only mean that `asRefCounted` can be used to try and get the data.
311      * No guarantee that it will succeed.
312      */
313     bool empty() @safe pure nothrow const @nogc {
314         return impl is null;
315     }
316 
317     T opCast(T : bool)() @safe pure nothrow const @nogc {
318         return !empty;
319     }
320 }
321 
322 @("shall only call the destructor one time")
323 @safe unittest {
324     size_t dtorcalled = 0;
325     struct S {
326         int x;
327         @safe ~this() {
328             if (x)
329                 dtorcalled++;
330         }
331 
332         @disable this(this);
333     }
334 
335     {
336         auto rc1 = S(1).refCounted;
337         assert(rc1.refCount == 1);
338         assert(rc1.impl.weakCnt == 1);
339         auto rc2 = rc1;
340         assert(rc2.refCount == 2);
341         assert(rc2.impl.weakCnt == 1);
342 
343         auto wrc1 = rc1.weakRef;
344         assert(wrc1.impl.useCnt == 2);
345         assert(wrc1.impl.weakCnt == 2);
346     }
347 
348     assert(dtorcalled == 1);
349 }
350 
351 @("shall destroy the object even though there are cycles because they are WeakRef")
352 @safe unittest {
353     size_t dtorcalled = 0;
354     struct S {
355         int x;
356         WeakRef!(typeof(this)) other;
357 
358         @safe ~this() {
359             if (x)
360                 dtorcalled++;
361         }
362 
363         @disable this(this);
364     }
365 
366     {
367         auto rc1 = S(1).refCounted;
368         auto rc2 = S(2).refCounted;
369 
370         rc1.get.other = rc2.weakRef;
371         rc2.get.other = rc1.weakRef;
372 
373         assert(rc1.impl.useCnt == 1);
374         assert(rc1.impl.weakCnt == 2);
375         assert(rc2.impl.useCnt == 1);
376         assert(rc2.impl.weakCnt == 2);
377     }
378 
379     assert(dtorcalled == 2);
380 }
381 
382 @("shall ref count an object stored in a Variant")
383 @system unittest {
384     import std.variant : Variant;
385     import std.typecons : tuple, Tuple;
386 
387     static struct S {
388         int x;
389     }
390 
391     auto rc = S(42).refCounted;
392 
393     {
394         Variant obj;
395 
396         obj = rc;
397         assert(rc.refCount == 2, "count incr when stored");
398 
399         obj = 42;
400         assert(rc.refCount == 1, "count decrease when obj is destroyed");
401 
402         {
403             obj = rc.weakRef;
404             assert(rc.refCount == 1, "the use count did not change");
405             assert(rc.impl.weakCnt == 2, "weak count incr");
406         }
407 
408         { // lets get the object back via the weak ref
409             auto tmpRef = obj.get!(WeakRef!S);
410             assert(rc.impl.weakCnt == 3);
411             auto tmpRc = tmpRef.asRefCounted;
412             assert(tmpRc.get.x == 42);
413         }
414         assert(rc.impl.weakCnt == 2);
415     }
416 
417     assert(rc.refCount == 1,
418             "when last ref of obj disappears the dtor is called. only one ref left");
419     assert(rc.impl.weakCnt == 1);
420 }
421 
422 @("shall ref count an object stored in nested Variant")
423 @system unittest {
424     import std.variant : Variant;
425     import std.typecons : tuple, Tuple;
426 
427     static struct S {
428         int x;
429     }
430 
431     auto rc = S(42).refCounted;
432 
433     {
434         auto obj = Variant(rc.weakRef);
435         assert(rc.refCount == 1, "the use count did not change");
436         assert(rc.impl.weakCnt == 2, "weak count incr");
437 
438         { // nested Variants call ctor/dtor as expected
439             auto obj2 = Variant(tuple(42, obj));
440             assert(rc.refCount == 1);
441             assert(rc.impl.weakCnt == 3);
442         }
443     }
444 
445     assert(rc.refCount == 1,
446             "when last ref of obj disappears the dtor is called. only one ref left");
447 }