PageRenderTime 45ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/concurrent/dotnet/Actor.cs

https://bitbucket.org/bedlaczech/fan-1.0
C# | 386 lines | 269 code | 61 blank | 56 comment | 60 complexity | fba76499f3ed3d9e0ecee7170191f27d MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //
  2. // Copyright (c) 2009, Brian Frank and Andy Frank
  3. // Licensed under the Academic Free License version 3.0
  4. //
  5. // History:
  6. // 30 Mar 09 Andy Frank Creation
  7. //
  8. using System;
  9. using System.Collections;
  10. using System.Threading;
  11. using Fanx.Util;
  12. namespace Fan.Sys
  13. {
  14. /// <summary>
  15. /// Actor is a worker who processes messages asynchronously.
  16. /// </summary>
  17. public class Actor : FanObj, Fanx.Util.ThreadPool.Work
  18. {
  19. //////////////////////////////////////////////////////////////////////////
  20. // Construction
  21. //////////////////////////////////////////////////////////////////////////
  22. public static Actor make(ActorPool pool) { return make(pool, null); }
  23. public static Actor make(ActorPool pool, Func receive)
  24. {
  25. Actor self = new Actor();
  26. make_(self, pool, receive);
  27. return self;
  28. }
  29. public static void make_(Actor self, ActorPool pool) { make_(self, pool, null); }
  30. public static void make_(Actor self, ActorPool pool, Func receive)
  31. {
  32. // check pool
  33. if (pool == null)
  34. throw NullErr.make("pool is null").val;
  35. // check receive method
  36. if (receive == null && self.@typeof().qname() == "concurrent::Actor")
  37. throw ArgErr.make("must supply receive func or subclass Actor").val;
  38. if (receive != null && !receive.isImmutable())
  39. throw NotImmutableErr.make("Receive func not immutable: " + receive).val;
  40. // init
  41. self.m_pool = pool;
  42. self.m_receive = receive;
  43. self.m_queue = new Queue();
  44. }
  45. public static Actor makeCoalescing(ActorPool pool, Func k, Func c) { return makeCoalescing(pool, k, c, null); }
  46. public static Actor makeCoalescing(ActorPool pool, Func k, Func c, Func r)
  47. {
  48. Actor self = new Actor();
  49. makeCoalescing_(self, pool, k, c, r);
  50. return self;
  51. }
  52. public static void makeCoalescing_(Actor self, ActorPool pool, Func k, Func c) { makeCoalescing_(self, pool, k, c, null); }
  53. public static void makeCoalescing_(Actor self, ActorPool pool, Func k, Func c, Func r)
  54. {
  55. if (k != null && !k.isImmutable())
  56. throw NotImmutableErr.make("Coalescing toKey func not immutable: " + k).val;
  57. if (c != null && !c.isImmutable())
  58. throw NotImmutableErr.make("Coalescing coalesce func not immutable: " + c).val;
  59. make_(self, pool, r);
  60. self.m_queue = new CoalescingQueue(k, c);
  61. }
  62. public Actor()
  63. {
  64. this.m_context = new Context(this);
  65. }
  66. //////////////////////////////////////////////////////////////////////////
  67. // Obj
  68. //////////////////////////////////////////////////////////////////////////
  69. public override Type @typeof()
  70. {
  71. if (m_type == null) m_type = Type.find("concurrent::Actor");
  72. return m_type;
  73. }
  74. private static Type m_type;
  75. //////////////////////////////////////////////////////////////////////////
  76. // Actor
  77. //////////////////////////////////////////////////////////////////////////
  78. public ActorPool pool() { return m_pool; }
  79. public Future send(object msg) { return _send(msg, null, null); }
  80. public Future sendLater(Duration d, object msg) { return _send(msg, d, null); }
  81. public Future sendWhenDone(Future f, object msg) { return _send(msg, null, f); }
  82. protected object receive(object msg)
  83. {
  84. if (m_receive != null) return m_receive.call(msg);
  85. System.Console.WriteLine("WARNING: " + @typeof() + ".receive not overridden");
  86. return null;
  87. }
  88. //////////////////////////////////////////////////////////////////////////
  89. // Utils
  90. //////////////////////////////////////////////////////////////////////////
  91. public static void sleep(Duration duration)
  92. {
  93. try
  94. {
  95. long ticks = duration.m_ticks;
  96. System.Threading.Thread.Sleep(new System.TimeSpan(ticks/100));
  97. }
  98. catch (ThreadInterruptedException e)
  99. {
  100. throw InterruptedErr.make(e).val;
  101. }
  102. }
  103. public static Map locals()
  104. {
  105. if (m_locals == null)
  106. m_locals = new Map(Sys.StrType, Sys.ObjType.toNullable());
  107. return m_locals;
  108. }
  109. [ThreadStatic] static Map m_locals;
  110. //////////////////////////////////////////////////////////////////////////
  111. // Implementation
  112. //////////////////////////////////////////////////////////////////////////
  113. private Future _send(object msg, Duration dur, Future whenDone)
  114. {
  115. // ensure immutable or safe copy
  116. msg = Sys.safe(msg);
  117. // don't deliver new messages to a stopped pool
  118. if (m_pool.isStopped()) throw Err.make("ActorPool is stopped").val;
  119. // get the future instance to manage this message's lifecycle
  120. Future f = new Future(msg);
  121. // either enqueue immediately or schedule with pool
  122. if (dur != null)
  123. m_pool.schedule(this, dur, f);
  124. else if (whenDone != null)
  125. whenDone.sendWhenDone(this, f);
  126. else
  127. f = _enqueue(f, true);
  128. return f;
  129. }
  130. internal Future _enqueue(Future f, bool coalesce)
  131. {
  132. lock (m_lock)
  133. {
  134. // attempt to coalesce
  135. if (coalesce)
  136. {
  137. Future c = m_queue.coalesce(f);
  138. if (c != null) return c;
  139. }
  140. // add to queue
  141. m_queue.add(f);
  142. // submit to thread pool if not submitted or current running
  143. if (!m_submitted)
  144. {
  145. m_submitted = true;
  146. m_pool.submit(this);
  147. }
  148. return f;
  149. }
  150. }
  151. public void _work()
  152. {
  153. // set locals for this actor
  154. m_locals = m_context.m_locals;
  155. Locale.setCur(m_context.m_locale);
  156. // process up to 100 messages before yielding the thread
  157. for (int count = 0; count < 100; count++)
  158. {
  159. // get next message, or if none pending we are done
  160. Future future = null;
  161. lock (m_lock) { future = m_queue.get(); }
  162. if (future == null) break;
  163. // dispatch the messge
  164. _dispatch(future);
  165. }
  166. // flush locals back to context
  167. m_context.m_locale = Locale.cur();
  168. // done dispatching, either clear the submitted
  169. // flag or resubmit to the thread pool
  170. lock (m_lock)
  171. {
  172. if (m_queue.size == 0)
  173. {
  174. m_submitted = false;
  175. }
  176. else
  177. {
  178. m_submitted = true;
  179. m_pool.submit(this);
  180. }
  181. }
  182. }
  183. internal void _dispatch(Future future)
  184. {
  185. try
  186. {
  187. if (future.isCancelled()) return;
  188. if (m_pool.m_killed) { future.cancel(); return; }
  189. future.set(receive(future.m_msg));
  190. }
  191. catch (Err.Val e)
  192. {
  193. future.err(e.m_err);
  194. }
  195. catch (System.Exception e)
  196. {
  197. future.err(Err.make(e));
  198. }
  199. }
  200. public void _kill()
  201. {
  202. // get/reset the pending queue
  203. Queue queue = null;
  204. lock (m_lock)
  205. {
  206. queue = this.m_queue;
  207. this.m_queue = new Queue();
  208. }
  209. // cancel all pending messages
  210. while (true)
  211. {
  212. Future future = queue.get();
  213. if (future == null) break;
  214. future.cancel();
  215. }
  216. }
  217. //////////////////////////////////////////////////////////////////////////
  218. // Queue
  219. //////////////////////////////////////////////////////////////////////////
  220. internal class Queue
  221. {
  222. public virtual Future get()
  223. {
  224. if (head == null) return null;
  225. Future f = head;
  226. head = f.m_next;
  227. if (head == null) tail = null;
  228. f.m_next = null;
  229. size--;
  230. return f;
  231. }
  232. public virtual void add(Future f)
  233. {
  234. if (tail == null) { head = tail = f; f.m_next = null; }
  235. else { tail.m_next = f; tail = f; }
  236. size++;
  237. }
  238. public virtual Future coalesce(Future f)
  239. {
  240. return null;
  241. }
  242. internal Future head, tail;
  243. internal int size;
  244. }
  245. //////////////////////////////////////////////////////////////////////////
  246. // CoalescingQueue
  247. //////////////////////////////////////////////////////////////////////////
  248. internal class CoalescingQueue : Queue
  249. {
  250. public CoalescingQueue(Func toKeyFunc, Func coalesceFunc)
  251. {
  252. this.toKeyFunc = toKeyFunc;
  253. this.coalesceFunc = coalesceFunc;
  254. }
  255. public override Future get()
  256. {
  257. Future f = base.get();
  258. if (f != null)
  259. {
  260. try
  261. {
  262. object key = toKey(f.m_msg);
  263. if (key != null) pending.Remove(key);
  264. }
  265. catch (System.Exception e)
  266. {
  267. Err.dumpStack(e);
  268. }
  269. }
  270. return f;
  271. }
  272. public override void add(Future f)
  273. {
  274. try
  275. {
  276. object key = toKey(f.m_msg);
  277. if (key != null) pending[key] = f;
  278. }
  279. catch (System.Exception e)
  280. {
  281. Err.dumpStack(e);
  282. }
  283. base.add(f);
  284. }
  285. public override Future coalesce(Future incoming)
  286. {
  287. object key = toKey(incoming.m_msg);
  288. if (key == null) return null;
  289. Future orig = (Future)pending[key];
  290. if (orig == null) return null;
  291. orig.m_msg = coalesce(orig.m_msg, incoming.m_msg);
  292. return orig;
  293. }
  294. private object toKey(object obj)
  295. {
  296. return toKeyFunc == null ? obj : toKeyFunc.call(obj);
  297. }
  298. private object coalesce(object orig, object incoming)
  299. {
  300. return coalesceFunc == null ? incoming : coalesceFunc.call(orig, incoming);
  301. }
  302. Func toKeyFunc, coalesceFunc;
  303. Hashtable pending = new Hashtable();
  304. }
  305. //////////////////////////////////////////////////////////////////////////
  306. // Context
  307. //////////////////////////////////////////////////////////////////////////
  308. internal class Context
  309. {
  310. internal Context(Actor actor) { m_actor = actor; }
  311. internal Actor m_actor;
  312. internal Map m_locals = new Map(Sys.StrType, Sys.ObjType.toNullable());
  313. internal Locale m_locale = Locale.cur();
  314. }
  315. //////////////////////////////////////////////////////////////////////////
  316. // Fields
  317. //////////////////////////////////////////////////////////////////////////
  318. internal readonly Context m_context; // mutable world state of actor
  319. private ActorPool m_pool; // pool controller
  320. private Func m_receive; // func to invoke on receive or null
  321. private object m_lock = new object(); // lock for message queue
  322. private Queue m_queue; // message queue linked list
  323. private bool m_submitted = false; // is actor submitted to thread pool
  324. }
  325. }