1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.concurrency.FuturePromise;
13 
14 import hunt.concurrency.Future;
15 import hunt.concurrency.Promise;
16 
17 import hunt.Exceptions;
18 import hunt.logging.ConsoleLogger;
19 
20 import core.atomic;
21 import core.sync.condition;
22 import core.sync.mutex;
23 import core.thread;
24 import std.format;
25 import std.datetime;
26 
27 /**
28  * 
29  */
30 class FuturePromise(T) : Future!T, Promise!T {
31 	private __gshared Exception COMPLETED;
32 	private shared bool _done = false;
33 	private bool _isResultAvaliable = false;
34 	private Exception _cause;
35 	private string _id;
36 	shared static this() {
37 		COMPLETED = new Exception("");
38 	}
39 
40 	this() {
41 	}
42 
43 	string id() {
44 		return _id;
45 	}
46 
47 	void id(string id) {
48 		_id = id;
49 	}
50 
51 static if(is(T == void)) {
52 	
53 	/**
54 	 * TODO: 
55 	 * 	1) keep this operation atomic
56 	 * 	2) return a flag to indicate whether this option is successful.
57 	 */
58 	void succeeded() {
59 		if (cas(&_done, false, true)) {
60 			_cause = COMPLETED;
61 			_isResultAvaliable = true;
62 		} else {
63 			warning("This promise has been done, and can't be set again.");
64 		}
65 	}
66 
67 } else {
68 
69 	/**
70 	 * TODO: 
71 	 * 	1) keep this operation atomic
72 	 * 	2) return a flag to indicate whether this option is successful.
73 	 */
74 	void succeeded(T result) {
75 		if (cas(&_done, false, true)) {
76 			_result = result;
77 			_cause = COMPLETED;
78 			_isResultAvaliable = true;
79 		} else {
80 			warning("This promise has been done, and can't be set again.");
81 		}
82 	}
83 	private T _result;
84 }
85 
86 	/**
87 	 * TODO: 
88 	 * 	1) keep this operation atomic
89 	 * 	2) return a flag to indicate whether this option is successful.
90 	 */
91 	void failed(Exception cause) {
92 		if (cas(&_done, false, true)) {
93 			_cause = cause;
94 			_isResultAvaliable = true;
95 		} else {
96 			warning("This promise has been done, and can't be set again.");
97 		}
98 	}
99 
100 	bool cancel(bool mayInterruptIfRunning) {
101 		if (cas(&_done, false, true)) {
102 			static if(!is(T == void)) {
103 				_result = T.init;
104 			}
105 			_cause = new CancellationException("");
106 			_isResultAvaliable = true;
107 			// _doneCondition.notifyAll();
108 			return true;
109 		}
110 		return false;
111 	}
112 
113 	bool isCancelled() {
114 		if (_done) {
115 			try {
116 				// _latch.await();
117 				// TODO: Tasks pending completion -@zhangxueping at 2019-12-26T15:18:42+08:00
118 				// 
119 			} catch (InterruptedException e) {
120 				throw new RuntimeException(e.msg);
121 			}
122 			return typeid(_cause) == typeid(CancellationException);
123 		}
124 		return false;
125 	}
126 
127 	bool isDone() {
128 		return _done;
129 	}
130 
131 	T get() {
132 		// waitting for the result
133 		version (HUNT_DEBUG) info("Waiting for a promise...");
134 		while(!_isResultAvaliable) {
135             Thread.yield();
136 			version(HUNT_DANGER_DEBUG) trace("Waiting for a promise");
137 		}
138 
139 		version (HUNT_DEBUG) info("Got a promise");
140 		assert(_cause !is null);
141 
142 		if (_cause is COMPLETED) {
143 			static if(is(T == void)) {
144 				return;
145 			} else {
146 				return _result;
147 			}
148 		}
149 
150 		CancellationException c = cast(CancellationException) _cause;
151 		if (c !is null) {
152 			version(HUNT_DEBUG) info("A promise cancelled.");
153 			throw c;
154 		}
155 		
156 		debug warning("Get a exception in a promise: ", _cause.msg);
157 		version (HUNT_DEBUG) warning(_cause);
158 		throw new ExecutionException(_cause);
159 	}
160 
161 	T get(Duration timeout) {
162 		// waitting for the result
163 		if(!_isResultAvaliable) {
164 			version (HUNT_DEBUG) {
165 				infof("Waiting for a promise in %s...", timeout);
166 			}
167             auto start = Clock.currTime;
168             while (!_isResultAvaliable && Clock.currTime < start + timeout) {
169                 Thread.yield();
170             }
171 
172 			if (!_isResultAvaliable) {
173 				debug warningf("Timeout for a promise in %s...", timeout);
174 				failed(new TimeoutException("Timeout in " ~ timeout.toString()));
175             }
176 
177 			version (HUNT_DEBUG) {
178 				auto dur = Clock.currTime - start;
179 				if(dur > 5.seconds) {
180 					warningf("Got a promise in %s", dur);
181 				} else {
182 					// infof("Got a promise in %s", dur);
183 				}
184 			}
185 		}		
186 
187 		if (_cause is COMPLETED) {
188 			static if(is(T == void)) {
189 				return;
190 			} else {
191 				return _result;
192 			}
193 		}
194 
195 		TimeoutException t = cast(TimeoutException) _cause;
196 		if (t !is null)
197 			throw t;
198 
199 		CancellationException c = cast(CancellationException) _cause;
200 		if (c !is null)
201 			throw c;
202 
203 		throw new ExecutionException(_cause.msg);
204 	}
205 
206 	override string toString() {
207 		static if(is(T == void)) {
208 			return format("FutureCallback@%x{%b, %b, void}", toHash(), _done, _cause is COMPLETED);
209 		} else {
210 			return format("FutureCallback@%x{%b, %b, %s}", toHash(), _done, _cause is COMPLETED, _result);
211 		}
212 	}
213 }