1 /**
2  Reference counting using the GC.
3 
4  The RefCounted struct simply stores the item in a GC block, and also adds a
5  root to that block. Once all known references to the block are removed
6  (tracked by a reference count in the block), then the block is removed, and
7  the destructor run. Since it's a root, it can run the full destructor of the
8  data underneath, without worrying about GC data being collected underneath it.
9 
10  This depends on the block not being involved in a cycle, which should be fine
11  for iopipes.
12 
13  Note that atomics are used for the reference count because the GC can destroy
14  things in other threads.
15  */
16 module iopipe.refc;
17 
18 /**
19  * A struct to ensure only one copy of the provided item exists.
20  *
21  * This differs from Phobos' std.typecons.RefCounted by using the GC to store
22  * the memory, instead of C's heap. The benefit here is that this version of
23  * RefCounted can be @safe.
24  *
25  * The block containing the item is pinned in the GC until all references are
26  * gone, which means the destructor will be run synchronously when the last
27  * reference is removed. Therefore, it is safe to store a RefCounted struct
28  * inside a GC allocated type.
29  */
30 struct RefCounted(T)
31 {
32     /// Constructor. the underlying T is constructed using the parameters.
33     this(Args...)(auto ref Args args)
34     {
35         import core.memory : GC;
36         // need to use untyped memory, so we don't get a dtor call by the GC.
37         import std.traits : hasIndirections;
38         import std.conv : emplace;
39         static if(hasIndirections!T)
40             auto rawMem = new void[Impl.sizeof];
41         else
42             auto rawMem = new ubyte[Impl.sizeof];
43         _impl = (() @trusted => cast(Impl*)rawMem.ptr)();
44         emplace(_impl, args);
45         () @trusted { GC.addRoot(_impl); }();
46     }
47 
48     private struct Impl
49     {
50         this(ref T _item)
51         {
52             import std.algorithm : move;
53             item = move(_item);
54         }
55 
56         this(Args...)(auto ref Args args)
57         {
58             item = T(args);
59         }
60         T item;
61         shared int _count = 1;
62     }
63 
64     /** Get a reference to the item. Note that if you store a reference to this
65     * item, it is possible the item will in the future be destroyed, but the
66     * memory will still be present (until the GC cleans it up).
67     */
68     ref T _get() return
69     {
70         assert(_impl, "Invalid refcounted access");
71         return _impl.item;
72     }
73 
74     this(this)
75     {
76         if(_impl)
77         {
78             import core.atomic;
79             _impl._count.atomicOp!"+="(1);
80         }
81     }
82 
83     ~this()
84     {
85         if(_impl)
86         {
87             assert(_impl._count >= 0, "Invalid count detected");
88             import core.atomic;
89             if(_impl._count.atomicOp!"-="(1) == 0)
90             {
91                 destroy(_impl.item);
92                 import core.memory : GC;
93                 () @trusted { GC.removeRoot(_impl); } ();
94             }
95             _impl = null;
96         }
97     }
98 
99     /// Assignment to another ref counted item.
100     void opAssign(RefCounted other)
101     {
102         import std.algorithm : swap;
103         swap(_impl, other._impl);
104     }
105 
106     /// Assignment to another T.
107     void opAssign(T other)
108     {
109         import std.algorithm : move;
110         move(other, _impl.item);
111     }
112 
113     /// Alias the item to this struct.
114     alias _get this;
115 
116 private:
117     private Impl * _impl;
118 }
119 
120 /// Return a ref counted version of the given item.
121 RefCounted!T refCounted(T)(auto ref T item)
122 {
123     return RefCounted!T(item);
124 }
125 
126 ///
127 @safe unittest
128 {
129     // note that destructor is called from the parameter to refCounted, so we
130     // must trigger only counting destruction of non-init instances of the
131     // struct.
132     size_t dtorcalled = 0;
133     struct S
134     {
135         int x;
136         @safe ~this() {if(x) dtorcalled++;}
137         @disable this(this);
138     }
139 
140     {
141         auto destroyme = S(1).refCounted;
142         auto dm2 = destroyme;
143         auto dm3 = destroyme;
144     }
145 
146     assert(dtorcalled == 1);
147 }