PageRenderTime 4292ms CodeModel.GetById 51ms RepoModel.GetById 18ms app.codeStats 0ms

/Squared/Threading/CancellationScope.cs

http://github.com/kevingadd/Fracture
C# | 295 lines | 222 code | 63 blank | 10 comment | 35 complexity | e598e41f6942a30fa74f1fdb679ea2ca MD5 | raw file
  1. #define TRACING
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Runtime.CompilerServices;
  8. using System.Text;
  9. using Squared.Util;
  10. // FIXME: This whole file needs unit tests
  11. using tTask = System.Threading.Tasks.Task;
  12. using CallContext = System.Runtime.Remoting.Messaging.CallContext;
  13. using TaskStatus = System.Threading.Tasks.TaskStatus;
  14. using System.Linq.Expressions;
  15. using Squared.Threading;
  16. namespace Squared.Threading.AsyncAwait {
  17. public class CancellationScope {
  18. /// <summary>
  19. /// Set this to false in order to allow cancellation scopes to be used on threads without an active scheduler.
  20. /// </summary>
  21. public static bool StrictMode = false;
  22. public class Registration {
  23. public readonly CancellationScope Scope;
  24. public readonly IWorkItemQueueTarget Scheduler;
  25. private Action Continuation;
  26. public Registration (IWorkItemQueueTarget scheduler) {
  27. Scope = CancellationScope.Current;
  28. Scheduler = scheduler;
  29. if ((Scheduler == null) && StrictMode)
  30. throw new InvalidOperationException("No implicitly active TaskScheduler on this thread.");
  31. }
  32. public Squared.Threading.OnComplete OnComplete (Action continuation) {
  33. if ((Continuation != null) && (Continuation != continuation))
  34. throw new InvalidOperationException("Continuation already registered");
  35. Continuation = continuation;
  36. return _OnComplete;
  37. }
  38. private void _OnComplete (IFuture f) {
  39. // FIXME: Is this right?
  40. if ((Scheduler == null) && !StrictMode)
  41. Continuation();
  42. else
  43. Scheduler.QueueWorkItem(Continuation);
  44. }
  45. public void ThrowIfCanceled () {
  46. Scope.ThrowIfCanceled();
  47. }
  48. }
  49. public struct CancellationScopeAwaiter : INotifyCompletion {
  50. public readonly CancellationScope Scope;
  51. public CancellationScopeAwaiter (CancellationScope scope)
  52. : this()
  53. {
  54. Scope = scope;
  55. IsCompleted = false;
  56. }
  57. public void OnCompleted (Action continuation) {
  58. CancellationUtil.UnpackContinuation(continuation, out Scope.Task);
  59. CancellationScope existingScope;
  60. if (CancellationScope.TryGet(Scope.Task, out existingScope)) {
  61. // HACK: In some cases a cancelled task will get resumed, which starts it over from the beginning.
  62. // This hits us and we will get asked to schedule a resume, but we should ignore it so that the task
  63. // stays dead as it should.
  64. #if TRACING
  65. Console.WriteLine("Rejecting attempted resurrection of task {0}", existingScope);
  66. #endif
  67. return;
  68. }
  69. CancellationScope.Set(Scope.Task, Scope);
  70. IsCompleted = true;
  71. continuation();
  72. }
  73. public bool IsCompleted {
  74. get;
  75. private set;
  76. }
  77. public CancellationScope GetResult () {
  78. Scope.ThrowIfCanceled();
  79. return Scope;
  80. }
  81. }
  82. public static readonly CancellationScope Null = new CancellationScope("<null>");
  83. private static readonly ConditionalWeakTable<tTask, CancellationScope> ScopeRegistry = new ConditionalWeakTable<tTask, CancellationScope>();
  84. private static int NextId;
  85. private static Future<CancellationScope> Reserved = null;
  86. public static CancellationScope Current {
  87. get {
  88. var result = (CancellationScope)CallContext.LogicalGetData("CancellationScope");
  89. if (result == null)
  90. result = Null;
  91. return result;
  92. }
  93. }
  94. public static void Set (tTask task, CancellationScope scope) {
  95. ScopeRegistry.Add(task, scope);
  96. }
  97. public static bool TryGet (tTask task, out CancellationScope result) {
  98. return ScopeRegistry.TryGetValue(task, out result);
  99. }
  100. public static Future<CancellationScope> Reserve () {
  101. if (Reserved != null)
  102. throw new Exception("Scope already reserved");
  103. Reserved = new Future<CancellationScope>();
  104. return Reserved;
  105. }
  106. public readonly string Description;
  107. public readonly string FilePath;
  108. public readonly int LineNumber;
  109. public readonly int Id;
  110. private UnorderedList<CancellationScope> Children;
  111. private tTask Task;
  112. private bool _IsCanceled;
  113. public CancellationScope (
  114. [CallerMemberName]
  115. string description = null,
  116. [CallerFilePath]
  117. string filePath = null,
  118. [CallerLineNumber]
  119. int lineNumber = 0
  120. ) {
  121. Id = ++NextId;
  122. Description = description;
  123. FilePath = filePath;
  124. LineNumber = lineNumber;
  125. }
  126. public CancellationScopeAwaiter GetAwaiter () {
  127. ThrowIfCanceled();
  128. // Important to Push here instead of doing so during the OnComplete call.
  129. // This ensures we're pushed into the right local call context.
  130. Push();
  131. return new CancellationScopeAwaiter(this);
  132. }
  133. public CancellationScope Parent {
  134. get;
  135. private set;
  136. }
  137. public bool IsCanceled {
  138. get {
  139. if (_IsCanceled)
  140. return true;
  141. else if (Parent != null)
  142. return Parent.IsCanceled;
  143. else
  144. return false;
  145. }
  146. }
  147. private void AddChild (CancellationScope child) {
  148. if (this == Null)
  149. return;
  150. if (Children == null)
  151. Children = new UnorderedList<CancellationScope>();
  152. Children.Add(child);
  153. }
  154. public CancellationScope Push () {
  155. Parent = Current;
  156. Parent.AddChild(this);
  157. CallContext.LogicalSetData("CancellationScope", this);
  158. return Parent;
  159. }
  160. public bool TryCancel () {
  161. if (this == Null)
  162. return false;
  163. if (Children != null) {
  164. CancellationScope childScope;
  165. using (var e = Children.GetEnumerator())
  166. while (e.GetNext(out childScope))
  167. childScope.TryCancel();
  168. }
  169. var wasCanceled = _IsCanceled;
  170. _IsCanceled = true;
  171. if (Task == null)
  172. return false;
  173. switch (Task.Status) {
  174. case TaskStatus.Canceled:
  175. case TaskStatus.Faulted:
  176. return true;
  177. case TaskStatus.RanToCompletion:
  178. return false;
  179. }
  180. return true;
  181. }
  182. public void ThrowIfCanceled () {
  183. if (IsCanceled)
  184. throw new OperationCanceledException(ToString() + " cancelled");
  185. }
  186. public override string ToString () {
  187. return String.Format("<scope #{0} {1}>", Id, Description);
  188. }
  189. }
  190. internal static class CancellationUtil {
  191. delegate Action TTryGetStateMachineForDebugger (Action continuation);
  192. delegate tTask TExtractTaskFromStateMachine (object stateMachine);
  193. static readonly TTryGetStateMachineForDebugger TryGetStateMachineForDebugger;
  194. static readonly Dictionary<Type, TExtractTaskFromStateMachine> ExtractorCache = new Dictionary<Type, TExtractTaskFromStateMachine>();
  195. static CancellationUtil () {
  196. var tMethodBuilderCore = System.Type.GetType("System.Runtime.CompilerServices.AsyncMethodBuilderCore", true);
  197. var mi = tMethodBuilderCore.GetMethod("TryGetStateMachineForDebugger", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
  198. TryGetStateMachineForDebugger = (TTryGetStateMachineForDebugger)Delegate.CreateDelegate(
  199. typeof(TTryGetStateMachineForDebugger), mi, true
  200. );
  201. }
  202. private static TExtractTaskFromStateMachine CreateExtractor (Type tMachine) {
  203. var fBuilder = tMachine.GetField("<>t__builder", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  204. var tBuilder = fBuilder.FieldType;
  205. var pTask = tBuilder.GetProperty("Task", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  206. {
  207. var pStateMachine = Expression.Parameter(typeof(object), "stateMachine");
  208. var eCast = Expression.Convert(pStateMachine, tMachine);
  209. var eBuilder = Expression.MakeMemberAccess(
  210. eCast, fBuilder
  211. );
  212. var eTask = Expression.MakeMemberAccess(
  213. eBuilder, pTask
  214. );
  215. var eLambda = Expression.Lambda<TExtractTaskFromStateMachine>(eTask, pStateMachine);
  216. return eLambda.Compile();
  217. }
  218. }
  219. public static void UnpackContinuation (Action continuation, out tTask task) {
  220. var stateMachine = TryGetStateMachineForDebugger(continuation);
  221. if (stateMachine == null)
  222. throw new Exception("Could not extract state machine from continuation");
  223. var machine = stateMachine.Target;
  224. var tMachine = machine.GetType();
  225. TExtractTaskFromStateMachine extractor;
  226. lock (ExtractorCache)
  227. if (!ExtractorCache.TryGetValue(tMachine, out extractor))
  228. ExtractorCache.Add(tMachine, extractor = CreateExtractor(tMachine));
  229. task = extractor(machine);
  230. }
  231. }
  232. }