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 }