1 /** 2 D implementation of Rust's std::sync::Mutex 3 */ 4 module fearless.sharing; 5 6 import fearless.from; 7 8 /** 9 A new exclusive reference to a payload of type T constructed from args. 10 Allocated on the GC to make sure its lifetime is infinite and therefore 11 safe to pass to other threads. 12 */ 13 auto gcExclusive(T, A...)(auto ref A args) { 14 import std.functional: forward; 15 return new Exclusive!T(forward!args); 16 } 17 18 /** 19 A new exclusive reference to a payload. 20 Allocated on the GC to make sure its lifetime is infinite and therefore 21 safe to pass to other threads. 22 23 This function sets the passed-in payload to payload.init to make sure 24 that no references to it can be unsafely used. 25 */ 26 auto gcExclusive(T)(ref T payload) if(!from!"std.traits".hasUnsharedAliasing!T) { 27 return new Exclusive!T(payload); 28 } 29 30 static if (is(typeof({import automem.ref_counted;}))) { 31 32 /** 33 A reference counted exclusive object (see above). 34 */ 35 auto rcExclusive(T, A...)(auto ref A args) { 36 import automem.ref_counted: RefCounted; 37 import std.functional: forward; 38 return RefCounted!(Exclusive!T)(forward!args); 39 } 40 41 auto rcExclusive(T, Allocator, Args...)(Allocator allocator, auto ref Args args) 42 if(from!"automem.traits".isAllocator!Allocator) 43 { 44 import automem.ref_counted: RefCounted; 45 import std.traits: hasMember; 46 47 enum isSingleton = hasMember!(Allocator, "instance"); 48 49 static if(isSingleton) 50 return RefCounted!(Exclusive!T, Allocator)(args); 51 else 52 return RefCounted!(Exclusive!T, Allocator)(allocator, args); 53 } 54 } 55 56 57 alias Exclusive(T) = shared(ExclusiveImpl!T); 58 59 60 /** 61 Provides @safe exclusive access (via a mutex) to a payload of type T. 62 Allows to share mutable data across threads safely. 63 */ 64 package struct ExclusiveImpl(T) { 65 66 import std.traits: hasUnsharedAliasing, isAggregateType; 67 68 import core.sync.mutex: Mutex; // TODO: make the mutex type a parameter 69 70 private T _payload; 71 private Mutex _mutex; 72 private bool _locked; 73 74 @disable this(this); 75 76 /** 77 The constructor is responsible for initialising the payload so that 78 it's not possible to escape it. 79 */ 80 this(A...)(auto ref A args) shared { 81 import std.functional: forward; 82 this._payload = T(forward!args); 83 init(); 84 } 85 86 static if(isAggregateType!T && !hasUnsharedAliasing!T) { 87 /** 88 Take a payload by ref in the case that it's safe, and set the original 89 to T.init. 90 */ 91 private this(ref T payload) shared { 92 import std.algorithm: move; 93 import std.traits: Unqual; 94 95 _payload = () @trusted { return cast(shared) move(payload); }(); 96 payload = payload.init; 97 98 init(); 99 } 100 } 101 102 private void init() shared { 103 this._mutex = new shared Mutex; 104 } 105 106 /** 107 Whether or not the mutex is locked. 108 */ 109 bool isLocked() shared const { 110 return _locked; 111 } 112 113 /** 114 Obtain exclusive access to the payload. The mutex is locked and 115 when the returned `Guard` object's lifetime is over the mutex 116 is unloked. 117 */ 118 auto lock() shared { 119 () @trusted { _mutex.lock_nothrow; }(); 120 _locked = true; 121 return Guard(&_payload, _mutex, &_locked); 122 } 123 124 alias borrow = lock; 125 126 // non-static didn't work - weird error messages 127 static struct Guard { 128 129 private shared T* _payload; 130 private shared Mutex _mutex; 131 private shared bool* _locked; 132 133 alias reference this; 134 135 ref T reference() @trusted return scope { 136 return *(cast(T*) _payload); 137 } 138 139 ~this() scope @trusted { 140 *_locked = false; 141 _mutex.unlock_nothrow(); 142 } 143 } 144 }