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 }