1 ///
2 module libasync.signal;
3 import std.traits;
4 
5 import libasync.types;
6 import libasync.events;
7 import core.thread;
8 import core.sync.mutex : Mutex;
9 import std.exception : assumeWontThrow;
10 
11 /// Enqueues a signal in the event loop of the AsyncSignal owner's thread,
12 /// which allows a foreign thread to trigger the callback handler safely.
13 shared final class AsyncSignal
14 {
15 	private void delegate() m_sgh;
16 nothrow:
17 private:
18 	Thread m_owner;
19 	EventLoop m_evLoop;
20 	fd_t m_evId;
21 	shared Mutex m_mutex;
22 
23 	void lock() @trusted const nothrow
24 	{ assumeWontThrow((m_mutex).lock()); }
25 
26 	void unlock() @trusted const nothrow
27 	{ assumeWontThrow((m_mutex).unlock()); }
28 
29 public:
30 
31 	///
32 	this(EventLoop evl)
33 	in {
34 		assert(evl !is null);
35 	}
36 	body {
37 		m_evLoop = cast(shared) evl;
38 		import core.thread : Thread;
39 		m_owner = cast(shared) Thread.getThis();
40 		m_mutex = new shared Mutex;
41 
42 		version(Posix) {
43 			static if (EPOLL) {
44 				import core.sys.posix.pthread : pthread_self;
45 				m_pthreadId = cast(shared)pthread_self();
46 			} else /* if KQUEUE */ {
47 				m_owner_id = cast(shared) g_threadId;
48 			}
49 		}
50 	}
51 
52 	///
53 	@property bool hasError() const
54 	{
55 		return (cast(EventLoop)m_evLoop).status.code != Status.OK;
56 	}
57 
58 	/// Used to diagnose errors when run() or kill() returns false
59 	@property StatusInfo status() const {
60 		return (cast(EventLoop)m_evLoop).status;
61 	}
62 
63 	/// Human-readable string describing the error
64 	@property string error() const {
65 		return (cast(EventLoop)m_evLoop).error;
66 	}
67 
68 	/// Registers the signal handler in the event loop
69 	bool run(void delegate() del)
70 	in {
71 		debug assert(Thread.getThis() is cast(Thread)m_owner);
72 	}
73 	body {
74 		lock();
75 		scope (exit) unlock();
76 
77 		m_sgh = cast(void delegate()) del;
78 
79 		m_evId = (cast(EventLoop) m_evLoop).run(this);
80 		if (m_evId != fd_t.init)
81 			return true;
82 		else
83 			return false;
84 	}
85 
86 	/// Cleans up underlying resources. This object must be run() again afterwards to be valid
87 	bool kill()
88 	in {
89 		debug assert(Thread.getThis() is cast(Thread)m_owner);
90 	}
91 	body {
92 		return (cast(EventLoop)m_evLoop).kill(cast(shared AsyncSignal) this);
93 	}
94 
95 	/// Triggers the handler in its local thread
96 	bool trigger(EventLoop evl) {
97 		lock();
98 		scope (exit) unlock();
99 		return evl.notify(m_evId, this);
100 	}
101 
102 	/// ditto
103 	bool trigger() {
104 		lock();
105 		scope (exit) unlock();
106 		return (cast(EventLoop)m_evLoop).notify(m_evId, this);
107 	}
108 
109 	/// Returns the Thread that created this object.
110 	@property Thread owner() const {
111 		lock();
112 		scope (exit) unlock();
113 		return cast(Thread) m_owner;
114 	}
115 
116 	///
117 	@property fd_t id() const {
118 		return m_evId;
119 	}
120 
121 package:
122 	version(Posix) mixin EvInfoMixinsShared;
123 
124 	void handler() {
125 		try m_sgh();
126 		catch (Throwable) {}
127 		return;
128 	}
129 }
130 
131 package shared struct SignalHandler {
132 	AsyncSignal ctxt;
133 	void function(shared AsyncSignal) fct;
134 
135 	void opCall(shared AsyncSignal ctxt) {
136 		assert(ctxt !is null);
137 		fct(ctxt);
138 		return;
139 	}
140 }
141 
142 
143 /**
144 	Determines if the given list of types has any non-immutable and unshared aliasing outside of their object tree.
145 
146 	The types in particular may only contain plain data, pointers or arrays to immutable or shared data, or references
147 	encapsulated in stdx.typecons.Isolated. Values that do not have unshared and unisolated aliasing are safe to be passed
148 	between threads.
149 */
150 template isWeaklyIsolated(T...)
151 {
152 	import std.typecons : Rebindable;
153 	static if (T.length == 0) enum bool isWeaklyIsolated = true;
154 	else static if (T.length > 1) enum bool isWeaklyIsolated = isWeaklyIsolated!(T[0 .. $/2]) && isWeaklyIsolated!(T[$/2 .. $]);
155 	else {
156 		static if(is(T[0] == immutable)) enum bool isWeaklyIsolated = true;
157 		else static if (is(T[0] == shared)) enum bool isWeaklyIsolated = true;
158 		else static if (isInstanceOf!(Rebindable, T[0])) enum bool isWeaklyIsolated = isWeaklyIsolated!(typeof(T[0].get()));
159 		else static if (is(T[0] : Throwable)) enum bool isWeaklyIsolated = true; // WARNING: this is unsafe, but needed for send/receive!
160 		else static if (is(typeof(T[0].__isIsolatedType))) enum bool isWeaklyIsolated = true;
161 		else static if (is(typeof(T[0].__isWeakIsolatedType))) enum bool isWeaklyIsolated = true;
162 		else static if (is(T[0] == class)) enum bool isWeaklyIsolated = false;
163 		else static if (is(T[0] == interface)) enum bool isWeaklyIsolated = false; // can't know if the implementation is isolated
164 		else static if (is(T[0] == delegate)) enum bool isWeaklyIsolated = T[0].stringof.endsWith(" shared"); // can't know to what a delegate points - FIXME: use something better than a string comparison
165 		else static if (isDynamicArray!(T[0])) enum bool isWeaklyIsolated = is(typeof(T[0].init[0]) == immutable);
166 		else static if (isAssociativeArray!(T[0])) enum bool isWeaklyIsolated = false; // TODO: be less strict here
167 		else static if (isSomeFunction!(T[0])) enum bool isWeaklyIsolated = true; // functions are immutable
168 		else static if (isPointer!(T[0])) enum bool isWeaklyIsolated = is(typeof(*T[0].init) == immutable) || is(typeof(*T[0].init) == shared);
169 		else static if (isAggregateType!(T[0])) enum bool isWeaklyIsolated = isWeaklyIsolated!(FieldTypeTuple!(T[0]));
170 		else enum bool isWeaklyIsolated = true;
171 	}
172 }