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