PageRenderTime 16ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/mordor/future.h

http://github.com/mozy/mordor
C Header | 282 lines | 231 code | 22 blank | 29 comment | 56 complexity | 55ca50bdfd347e5d6901585e27b9b072 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #ifndef __MORDOR_FUTURE_H__
  2. #define __MORDOR_FUTURE_H__
  3. // Copyright (c) 2009 - Mozy, Inc.
  4. #include <bitset>
  5. #include <boost/bind.hpp>
  6. #include <boost/function.hpp>
  7. #include <boost/noncopyable.hpp>
  8. #include "assert.h"
  9. #include "atomic.h"
  10. #include "fiber.h"
  11. #include "scheduler.h"
  12. namespace Mordor {
  13. struct Void;
  14. template <class T = Void>
  15. class Future : boost::noncopyable
  16. {
  17. template <class Iterator>
  18. friend void waitAll(Iterator start, Iterator end);
  19. template <class Iterator>
  20. friend size_t waitAny(Iterator start, Iterator end);
  21. public:
  22. Future(boost::function<void (const T &)> dg = NULL, Scheduler *scheduler = NULL)
  23. : m_fiber(0),
  24. m_scheduler(scheduler),
  25. m_dg(dg),
  26. m_t()
  27. {
  28. MORDOR_ASSERT(dg || !scheduler);
  29. if (m_dg)
  30. m_fiber = 0x2;
  31. }
  32. /// Suspend this Fiber, and wait for the future to become signalled
  33. T &wait()
  34. {
  35. MORDOR_ASSERT(!m_scheduler);
  36. MORDOR_ASSERT(!m_dg);
  37. m_scheduler = Scheduler::getThis();
  38. intptr_t newValue = (intptr_t)Fiber::getThis().get();
  39. MORDOR_ASSERT(!(newValue & 0x1));
  40. intptr_t currentValue = atomicCompareAndSwap(m_fiber, newValue, (intptr_t)0);
  41. // Not signalled yet
  42. if (currentValue == 0) {
  43. Scheduler::yieldTo();
  44. // Make sure we got signalled
  45. MORDOR_ASSERT(m_fiber & 0x1);
  46. } else if (currentValue == 0x1) {
  47. // Already signalled; just return
  48. } else {
  49. MORDOR_NOTREACHED();
  50. }
  51. return m_t;
  52. }
  53. /// For signallers to set the result; once signalled should not be modified
  54. T& result() { MORDOR_ASSERT(!(m_fiber & 0x1)); return m_t; }
  55. /// Signal this Future
  56. void signal()
  57. {
  58. intptr_t newValue = m_fiber, oldValue;
  59. if (newValue == 0x2) {
  60. MORDOR_ASSERT(m_dg);
  61. if (m_scheduler)
  62. m_scheduler->schedule(boost::bind(m_dg, boost::cref(m_t)));
  63. else
  64. m_dg(m_t);
  65. return;
  66. }
  67. do {
  68. oldValue = newValue;
  69. newValue = oldValue | 0x1;
  70. } while ( (newValue = atomicCompareAndSwap(m_fiber, newValue, oldValue)) != oldValue);
  71. newValue &= ~0x1;
  72. // Somebody was previously waiting
  73. if (newValue) {
  74. MORDOR_ASSERT(m_scheduler);
  75. MORDOR_ASSERT(!m_dg);
  76. m_scheduler->schedule(((Fiber *)newValue)->shared_from_this());
  77. }
  78. }
  79. void reset()
  80. {
  81. m_fiber = 0;
  82. if (!m_dg)
  83. m_scheduler = NULL;
  84. }
  85. private:
  86. // We're going to stuff a couple of things into m_fiber, and do some bit
  87. // manipulation, so it's going to be easier to declare it as intptr_t
  88. // m_fiber = NULL if not signalled, and not waiting
  89. // m_fiber & 0x1 if signalled
  90. // m_fiber & ~0x1 if waiting
  91. // Note that m_fiber will *not* point to a fiber if m_dg is valid
  92. intptr_t m_fiber;
  93. Scheduler *m_scheduler;
  94. boost::function<void (const T &)> m_dg;
  95. T m_t;
  96. };
  97. template <>
  98. class Future<Void> : boost::noncopyable
  99. {
  100. template <class Iterator>
  101. friend void waitAll(Iterator start, Iterator end);
  102. template <class Iterator>
  103. friend size_t waitAny(Iterator start, Iterator end);
  104. public:
  105. Future(boost::function<void ()> dg = NULL, Scheduler *scheduler = NULL)
  106. : m_fiber(0),
  107. m_scheduler(scheduler),
  108. m_dg(dg)
  109. {
  110. MORDOR_ASSERT(m_dg || !scheduler);
  111. if (m_dg)
  112. m_fiber = 0x2;
  113. }
  114. /// Suspend this Fiber, and wait for the future to become signalled
  115. void wait()
  116. {
  117. MORDOR_ASSERT(!m_scheduler);
  118. MORDOR_ASSERT(!m_dg);
  119. m_scheduler = Scheduler::getThis();
  120. intptr_t newValue = (intptr_t)Fiber::getThis().get();
  121. MORDOR_ASSERT(!(newValue & 0x1));
  122. intptr_t currentValue = atomicCompareAndSwap(m_fiber, newValue, (intptr_t)0);
  123. // Not signalled yet
  124. if (currentValue == 0) {
  125. Scheduler::yieldTo();
  126. // Make sure we got signalled
  127. MORDOR_ASSERT(m_fiber & 0x1);
  128. } else if (currentValue == 0x1) {
  129. // Already signalled; just return
  130. } else {
  131. MORDOR_NOTREACHED();
  132. }
  133. }
  134. /// Signal this Future
  135. void signal()
  136. {
  137. intptr_t newValue = m_fiber, oldValue;
  138. if (newValue == 0x2) {
  139. MORDOR_ASSERT(m_dg);
  140. if (m_scheduler)
  141. m_scheduler->schedule(m_dg);
  142. else
  143. m_dg();
  144. return;
  145. }
  146. do {
  147. oldValue = newValue;
  148. newValue = oldValue | 0x1;
  149. } while ( (newValue = atomicCompareAndSwap(m_fiber, newValue, oldValue)) != oldValue);
  150. newValue &= ~0x1;
  151. // Somebody was previously waiting
  152. if (newValue) {
  153. MORDOR_ASSERT(m_scheduler);
  154. MORDOR_ASSERT(!m_dg);
  155. m_scheduler->schedule(((Fiber *)newValue)->shared_from_this());
  156. }
  157. }
  158. void reset()
  159. {
  160. m_fiber = 0;
  161. if (!m_dg)
  162. m_scheduler = NULL;
  163. }
  164. private:
  165. /// @return If the future was already signalled
  166. bool startWait()
  167. {
  168. MORDOR_ASSERT(!m_scheduler);
  169. MORDOR_ASSERT(!m_dg);
  170. m_scheduler = Scheduler::getThis();
  171. intptr_t newValue = (intptr_t)Fiber::getThis().get();
  172. MORDOR_ASSERT(!(newValue & 0x1));
  173. intptr_t currentValue = atomicCompareAndSwap(m_fiber, newValue, (intptr_t)0);
  174. if (currentValue == 0) {
  175. return false;
  176. } else if (currentValue == 0x1) {
  177. return true;
  178. } else {
  179. MORDOR_NOTREACHED();
  180. }
  181. }
  182. /// @return If the future was already signalled
  183. bool cancelWait()
  184. {
  185. MORDOR_ASSERT(!m_dg);
  186. intptr_t oldValue = m_fiber;
  187. if (oldValue & 1)
  188. return true;
  189. oldValue = atomicCompareAndSwap(m_fiber, (intptr_t)0, oldValue);
  190. if (oldValue & 1)
  191. return true;
  192. m_scheduler = NULL;
  193. return false;
  194. }
  195. private:
  196. // We're going to stuff a couple of things into m_fiber, and do some bit
  197. // manipulation, so it's going to be easier to declare it as intptr_t
  198. // m_fiber = NULL if not signalled, and not waiting
  199. // m_fiber & 0x1 if signalled
  200. // m_fiber & ~0x1 if waiting
  201. // Note that m_fiber will *not* point to a fiber if m_dg is valid
  202. intptr_t m_fiber;
  203. Scheduler *m_scheduler;
  204. boost::function<void ()> m_dg;
  205. };
  206. template <class Iterator>
  207. void waitAll(Iterator first, Iterator last)
  208. {
  209. MORDOR_ASSERT(first != last);
  210. size_t yieldsNeeded = 1;
  211. for (; first != last; ++first) {
  212. if (!first->startWait())
  213. ++yieldsNeeded;
  214. }
  215. while (--yieldsNeeded) Scheduler::yieldTo();
  216. }
  217. template <class Iterator>
  218. size_t waitAny(Iterator first, Iterator last)
  219. {
  220. MORDOR_ASSERT(first != last);
  221. size_t result = ~0u;
  222. size_t index = 0u;
  223. // Optimize first one
  224. if (first->startWait())
  225. return 0;
  226. Iterator it = first;
  227. ++it;
  228. while (it != last) {
  229. ++index;
  230. if (it->startWait()) {
  231. --it;
  232. result = index;
  233. break;
  234. }
  235. ++it;
  236. }
  237. size_t yieldsNeeded = 1;
  238. if (it == last) {
  239. --yieldsNeeded;
  240. Scheduler::yieldTo();
  241. --it;
  242. }
  243. while (it != first) {
  244. if (it->cancelWait()) {
  245. result = index;
  246. ++yieldsNeeded;
  247. }
  248. --it;
  249. --index;
  250. }
  251. if (it->cancelWait()) {
  252. result = 0;
  253. ++yieldsNeeded;
  254. }
  255. MORDOR_ASSERT(result != ~0u);
  256. while (--yieldsNeeded) Scheduler::yieldTo();
  257. return result;
  258. }
  259. }
  260. #endif