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 }