From 90a1f51b6501ea178df4a40095833c8acd8a9442 Mon Sep 17 00:00:00 2001 From: "Artem.Bukhonov" Date: Thu, 13 Aug 2015 22:09:18 +0300 Subject: [PATCH 01/21] Special evaluator exception type (EvaluatorExceptionThrownException) that wraps target process exception that occurred during runtime call. This allows to inspect exception objects in watch window if eval thrown an exception. --- .../Mono.Debugging.Client/ObjectValue.cs | 15 +++++++++++++-- .../ExpressionEvaluator.cs | 16 +++++++++++++++- .../ObjectValueAdaptor.cs | 2 ++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs b/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs index a71c2f718..13055da1a 100644 --- a/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs +++ b/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs @@ -32,6 +32,7 @@ using System.Linq; using Mono.Debugging.Backend; +using Mono.Debugging.Evaluation; namespace Mono.Debugging.Client { @@ -151,8 +152,18 @@ public static ObjectValue CreateError (IObjectValueSource source, ObjectPath pat val.value = value; return val; } - - public static ObjectValue CreateImplicitNotSupported (IObjectValueSource source, ObjectPath path, string typeName, ObjectValueFlags flags) + + public static ObjectValue CreateEvaluationException (EvaluationContext ctx, IObjectValueSource source, ObjectPath path, EvaluatorExceptionThrownException exception, + ObjectValueFlags flags = ObjectValueFlags.None) + { + var error = CreateError (source, path, exception.ExceptionTypeName, "Exception was thrown", flags); + var exceptionReference = LiteralValueReference.CreateTargetObjectLiteral (ctx, "Exception", exception.Exception); + var exceptionValue = exceptionReference.CreateObjectValue (ctx.Options); + error.children = new List {exceptionValue}; + return error; + } + + public static ObjectValue CreateImplicitNotSupported (IObjectValueSource source, ObjectPath path, string typeName, ObjectValueFlags flags) { var val = Create (source, path, typeName); val.flags = flags | ObjectValueFlags.ImplicitNotSupported; diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs index 059873e12..03ee28739 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs @@ -199,14 +199,28 @@ public virtual ValueReference GetCurrentException (EvaluationContext ctx) [Serializable] public class EvaluatorException: Exception { + protected EvaluatorException (SerializationInfo info, StreamingContext context) : base (info, context) { } - public EvaluatorException (string msg, params object[] args): base (string.Format (msg, args)) + public EvaluatorException (string msg, params object[] args): base (string.Format(msg, args)) + { + } + } + + [Serializable] + public class EvaluatorExceptionThrownException : EvaluatorException + { + public EvaluatorExceptionThrownException (object exception, string exceptionTypeName) : base ("Exception is thrown") { + Exception = exception; + ExceptionTypeName = exceptionTypeName; } + + public object Exception { get; private set; } + public string ExceptionTypeName { get; private set; } } [Serializable] diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs index 1376cedb1..99d8e3484 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs @@ -1355,6 +1355,8 @@ public ObjectValue GetExpressionValue (EvaluationContext ctx, string exp) return ObjectValue.CreateImplicitNotSupported (ctx.ExpressionValueSource, new ObjectPath (exp), "", ObjectValueFlags.None); } catch (NotSupportedExpressionException ex) { return ObjectValue.CreateNotSupported (ctx.ExpressionValueSource, new ObjectPath (exp), "", ex.Message, ObjectValueFlags.None); + } catch (EvaluatorExceptionThrownException ex) { + return ObjectValue.CreateEvaluationException (ctx, ctx.ExpressionValueSource, new ObjectPath (exp), ex); } catch (EvaluatorException ex) { return ObjectValue.CreateError (ctx.ExpressionValueSource, new ObjectPath (exp), "", ex.Message, ObjectValueFlags.None); } catch (Exception ex) { From b72dfaf15caa9b45fed4ad18de2fbfb07240550b Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Mon, 19 Dec 2016 21:13:34 +0300 Subject: [PATCH 02/21] EvaluatorExceptionThrownException handling in ValueReference. This allows to properly show an exception occurred during property evaluation. --- Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs index dc207ae17..9e23167c1 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs @@ -106,6 +106,8 @@ public ObjectValue CreateObjectValue (EvaluationOptions options) return DC.ObjectValue.CreateImplicitNotSupported (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), Flags); } catch (NotSupportedExpressionException ex) { return DC.ObjectValue.CreateNotSupported (this, new ObjectPath (Name), Context.Adapter.GetDisplayTypeName (GetContext (options), Type), ex.Message, Flags); + } catch (EvaluatorExceptionThrownException ex) { + return DC.ObjectValue.CreateEvaluationException (Context, Context.ExpressionValueSource, new ObjectPath (Name), ex); } catch (EvaluatorException ex) { return DC.ObjectValue.CreateError (this, new ObjectPath (Name), "", ex.Message, Flags); } catch (Exception ex) { From cac578c5f14a818e4e1e9469b90b01e04d314654 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 13 May 2016 21:20:44 +0300 Subject: [PATCH 03/21] Evaluation exception wrapping in CreateObjectValue. Ignore eval exceptions occured during ToString() calls in type presentation --- .../Mono.Debugging.Evaluation/ObjectValueAdaptor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs index 99d8e3484..6afbfd479 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs @@ -91,6 +91,8 @@ public ObjectValue CreateObjectValue (EvaluationContext ctx, IObjectValueSource return CreateObjectValueImpl (ctx, source, path, obj, flags); } catch (EvaluatorAbortedException ex) { return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags); + } catch (EvaluatorExceptionThrownException ex) { + return ObjectValue.CreateEvaluationException (ctx, source, path, ex); } catch (EvaluatorException ex) { return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags); } catch (Exception ex) { @@ -1111,6 +1113,8 @@ public virtual object TargetObjectToObject (EvaluationContext ctx, object obj) return new EvaluationResult ("{" + CallToString (ctx, obj) + "}"); } catch (TimeOutException) { // ToString() timed out, fall back to default behavior. + } catch (EvaluatorExceptionThrownException e) { + // ToString() call thrown exception, fall back to default behavior. } } From 54a8ed0c646a9f6af8c8742687cc2146e91c6b72 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Wed, 14 Sep 2016 01:22:00 +0300 Subject: [PATCH 04/21] Separate lock object. Get rid of useful Monitor.Pulse() --- .../AsyncOperationManager.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index ea69b7424..05c965c76 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -34,7 +34,9 @@ namespace Mono.Debugging.Evaluation { public class AsyncOperationManager: IDisposable { - List operationsToCancel = new List (); + readonly List operationsToCancel = new List (); + readonly object operationsSync = new object (); + internal bool Disposing; public void Invoke (AsyncOperation methodCall, int timeout) @@ -42,7 +44,7 @@ public void Invoke (AsyncOperation methodCall, int timeout) methodCall.Aborted = false; methodCall.Manager = this; - lock (operationsToCancel) { + lock (operationsSync) { operationsToCancel.Add (methodCall); methodCall.Invoke (); } @@ -51,9 +53,8 @@ public void Invoke (AsyncOperation methodCall, int timeout) if (!methodCall.WaitForCompleted (timeout)) { bool wasAborted = methodCall.Aborted; methodCall.InternalAbort (); - lock (operationsToCancel) { + lock (operationsSync) { operationsToCancel.Remove (methodCall); - ST.Monitor.PulseAll (operationsToCancel); } if (wasAborted) throw new EvaluatorAbortedException (); @@ -65,9 +66,8 @@ public void Invoke (AsyncOperation methodCall, int timeout) methodCall.WaitForCompleted (System.Threading.Timeout.Infinite); } - lock (operationsToCancel) { + lock (operationsSync) { operationsToCancel.Remove (methodCall); - ST.Monitor.PulseAll (operationsToCancel); if (methodCall.Aborted) { throw new EvaluatorAbortedException (); } @@ -81,7 +81,7 @@ public void Invoke (AsyncOperation methodCall, int timeout) public void Dispose () { Disposing = true; - lock (operationsToCancel) { + lock (operationsSync) { foreach (AsyncOperation op in operationsToCancel) { op.InternalShutdown (); } @@ -91,7 +91,7 @@ public void Dispose () public void AbortAll () { - lock (operationsToCancel) { + lock (operationsSync) { foreach (AsyncOperation op in operationsToCancel) op.InternalAbort (); } From f3e1fee653c977e9f4a952d29efeb17c00010022 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Wed, 9 Nov 2016 02:39:08 +0300 Subject: [PATCH 05/21] Async operations rewritten to Tasks --- Mono.Debugging.Soft/SoftDebuggerAdaptor.cs | 165 ++++------ Mono.Debugging.Soft/SoftEvaluationContext.cs | 15 +- .../AsyncEvaluationTracker.cs | 2 +- .../AsyncOperationBase.cs | 88 ++++++ .../AsyncOperationManager.cs | 299 ++++++++---------- .../IAsyncOperation.cs | 22 ++ .../ObjectValueAdaptor.cs | 6 +- Mono.Debugging/Mono.Debugging.csproj | 2 + 8 files changed, 312 insertions(+), 287 deletions(-) create mode 100644 Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs create mode 100644 Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs diff --git a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs index 750114d02..21fd95142 100644 --- a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs +++ b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs @@ -33,7 +33,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; - +using System.Threading.Tasks; using Mono.Debugger.Soft; using Mono.Debugging.Backend; using Mono.Debugging.Evaluation; @@ -2145,30 +2145,36 @@ static string MirrorStringToString (EvaluationContext ctx, StringMirror mirror) } } - class MethodCall: AsyncOperation + internal class SoftOperationResult : OperationResult { - readonly InvokeOptions options = InvokeOptions.DisableBreakpoints | InvokeOptions.SingleThreaded; + public SoftOperationResult (Value result, bool resultIsException, Value[] outArgs) : base (result, resultIsException) + { + OutArgs = outArgs; + } + + public Value[] OutArgs { get; private set; } + } - readonly ManualResetEvent shutdownEvent = new ManualResetEvent (false); + internal class SoftMethodCall: AsyncOperationBase + { + readonly InvokeOptions options = InvokeOptions.DisableBreakpoints | InvokeOptions.SingleThreaded; readonly SoftEvaluationContext ctx; readonly MethodMirror function; readonly Value[] args; - readonly object obj; - IAsyncResult handle; - Exception exception; - IInvokeResult result; - - public MethodCall (SoftEvaluationContext ctx, MethodMirror function, object obj, Value[] args, bool enableOutArgs) + readonly IInvocableMethodOwnerMirror obj; + IInvokeAsyncResult invokeAsyncResult; + + public SoftMethodCall (SoftEvaluationContext ctx, MethodMirror function, IInvocableMethodOwnerMirror obj, Value[] args, bool enableOutArgs) { this.ctx = ctx; this.function = function; this.obj = obj; this.args = args; if (enableOutArgs) { - this.options |= InvokeOptions.ReturnOutArgs; + options |= InvokeOptions.ReturnOutArgs; } if (function.VirtualMachine.Version.AtLeast (2, 40)) { - this.options |= InvokeOptions.Virtual; + options |= InvokeOptions.Virtual; } } @@ -2178,113 +2184,60 @@ public override string Description { } } - public override void Invoke () + protected override void AfterCancelledImpl (int elapsedAfterCancelMs) { - try { - var invocableMirror = obj as IInvocableMethodOwnerMirror; - if (invocableMirror != null) { - var optionsToInvoke = options; - if (obj is StructMirror) { - optionsToInvoke |= InvokeOptions.ReturnOutThis; - } - handle = invocableMirror.BeginInvokeMethod (ctx.Thread, function, args, optionsToInvoke, null, null); - } - else - throw new ArgumentException ("Soft debugger method calls cannot be invoked on objects of type " + obj.GetType ().Name); - } catch (InvocationException ex) { - ctx.Session.StackVersion++; - exception = ex; - } catch (Exception ex) { - ctx.Session.StackVersion++; - DebuggerLoggingService.LogError ("Error in soft debugger method call thread on " + GetInfo (), ex); - exception = ex; - } } - public override void Abort () - { - if (handle is IInvokeAsyncResult) { - var info = GetInfo (); - DebuggerLoggingService.LogMessage ("Aborting invocation of " + info); - ((IInvokeAsyncResult) handle).Abort (); - // Don't wait for the abort to finish. The engine will do it. - } else { - throw new NotSupportedException (); - } - } - - public override void Shutdown () - { - shutdownEvent.Set (); - } - - void EndInvoke () + protected override Task> InvokeAsyncImpl (CancellationToken token) { try { - result = ((IInvocableMethodOwnerMirror) obj).EndInvokeMethodWithResult (handle); - } catch (InvocationException ex) { - if (!Aborting && ex.Exception != null) { - string ename = ctx.Adapter.GetValueTypeName (ctx, ex.Exception); - var vref = ctx.Adapter.GetMember (ctx, null, ex.Exception, "Message"); - - exception = vref != null ? new Exception (ename + ": " + (string) vref.ObjectValue) : new Exception (ename); - return; + var optionsToInvoke = options; + if (obj is StructMirror) { + optionsToInvoke |= InvokeOptions.ReturnOutThis; } - exception = ex; - } catch (Exception ex) { - DebuggerLoggingService.LogError ("Error in soft debugger method call thread on " + GetInfo (), ex); - exception = ex; - } finally { - ctx.Session.StackVersion++; - } - } - - string GetInfo () - { - try { - TypeMirror type = null; - if (obj is ObjectMirror) - type = ((ObjectMirror)obj).Type; - else if (obj is TypeMirror) - type = (TypeMirror)obj; - else if (obj is StructMirror) - type = ((StructMirror)obj).Type; - return string.Format ("method {0} on object {1}", - function == null? "[null]" : function.FullName, - type == null? "[null]" : type.FullName); - } catch (Exception ex) { - DebuggerLoggingService.LogError ("Error getting info for SDB MethodCall", ex); - return ""; + var tcs = new TaskCompletionSource> (); + invokeAsyncResult = (IInvokeAsyncResult)obj.BeginInvokeMethod (ctx.Thread, function, args, optionsToInvoke, ar => { + try { + var endInvokeResult = obj.EndInvokeMethodWithResult (ar); + token.ThrowIfCancellationRequested (); + tcs.SetResult (new SoftOperationResult (endInvokeResult.Result, false, endInvokeResult.OutArgs)); + } + catch (InvocationException ex) { + // throw OCE if cancelled + token.ThrowIfCancellationRequested (); + if (ex.Exception != null) { + tcs.SetResult (new SoftOperationResult (ex.Exception, true, null)); + } + else { + tcs.SetException (new EvaluatorException ("Target method has thrown an exception but the exception object is inaccessible")); + } + } + catch (Exception e) { + tcs.SetException (e); + } + finally { + UpdateSessionState (); + } + }, null); + return tcs.Task; + } catch (Exception) { + UpdateSessionState (); + throw; } } - public override bool WaitForCompleted (int timeout) + void UpdateSessionState () { - if (handle == null) - return true; - int res = WaitHandle.WaitAny (new WaitHandle[] { handle.AsyncWaitHandle, shutdownEvent }, timeout); - if (res == 0) { - EndInvoke (); - return true; - } - // Return true if shut down. - return res == 1; + ctx.Session.StackVersion++; } - public Value ReturnValue { - get { - if (exception != null) - throw new EvaluatorException (exception.Message); - return result.Result; - } - } - - public Value[] OutArgs { - get { - if (exception != null) - throw new EvaluatorException (exception.Message); - return result.OutArgs; + protected override void CancelImpl () + { + if (invokeAsyncResult == null) { + DebuggerLoggingService.LogError ("invokeAsyncResult is null", new ArgumentNullException ("invokeAsyncResult")); + return; } + invokeAsyncResult.Abort (); } } } diff --git a/Mono.Debugging.Soft/SoftEvaluationContext.cs b/Mono.Debugging.Soft/SoftEvaluationContext.cs index 560e385b7..c9c28ab15 100644 --- a/Mono.Debugging.Soft/SoftEvaluationContext.cs +++ b/Mono.Debugging.Soft/SoftEvaluationContext.cs @@ -190,16 +190,19 @@ Value RuntimeInvoke (MethodMirror method, object target, Value[] values, bool en DC.DebuggerLoggingService.LogMessage ("Thread state before evaluation is {0}", threadState); throw new EvaluatorException ("Evaluation is not allowed when the thread is in 'Wait' state"); } - var mc = new MethodCall (this, method, target, values, enableOutArgs); + var invocableMirror = target as IInvocableMethodOwnerMirror; + if (invocableMirror == null) + throw new ArgumentException ("Soft debugger method calls cannot be invoked on objects of type " + target.GetType ().Name); + var mc = new SoftMethodCall (this, method, invocableMirror, values, enableOutArgs); //Since runtime is returning NOT_SUSPENDED error if two methods invokes are executed //at same time we have to lock invoking to prevent this... lock (method.VirtualMachine) { - Adapter.AsyncExecute (mc, Options.EvaluationTimeout); - } - if (enableOutArgs) { - outArgs = mc.OutArgs; + var result = (SoftOperationResult)Adapter.InvokeSync (mc, Options.EvaluationTimeout).ThrowIfException (this); + if (enableOutArgs) { + outArgs = result.OutArgs; + } + return result.Result; } - return mc.ReturnValue; } } diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs index 020603133..ce6010777 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs @@ -41,7 +41,7 @@ namespace Mono.Debugging.Evaluation /// will then be made asynchronous and the Run method will immediately return an ObjectValue /// with the Evaluating state. /// - public class AsyncEvaluationTracker: RemoteFrameObject, IObjectValueUpdater, IDisposable + public class AsyncEvaluationTracker: IObjectValueUpdater, IDisposable { Dictionary asyncCallbacks = new Dictionary (); Dictionary asyncResults = new Dictionary (); diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs new file mode 100644 index 000000000..62fe8c90b --- /dev/null +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class OperationResult + { + public TValue Result { get; private set; } + public bool ResultIsException { get; private set; } + + public OperationResult (TValue result, bool resultIsException) + { + Result = result; + ResultIsException = resultIsException; + } + } + + public static class OperationResultEx + { + public static OperationResult ThrowIfException (this OperationResult result, EvaluationContext ctx) + { + if (!result.ResultIsException) + return result; + var exceptionTypeName = ctx.Adapter.GetValueTypeName (ctx, result.Result); + throw new EvaluatorExceptionThrownException (result.Result, exceptionTypeName); + } + } + + public interface IAsyncOperationBase + { + Task RawTask { get; } + string Description { get; } + void AfterCancelled (int elapsedAfterCancelMs); + } + + public abstract class AsyncOperationBase : IAsyncOperationBase + { + public Task> Task { get; protected set; } + + public Task RawTask + { + get + { + return Task; + } + } + + public abstract string Description { get; } + + public void AfterCancelled (int elapsedAfterCancelMs) + { + try { + AfterCancelledImpl (elapsedAfterCancelMs); + } + catch (Exception e) { + DebuggerLoggingService.LogError ("AfterCancelledImpl() thrown an exception", e); + } + } + + protected abstract void AfterCancelledImpl (int elapsedAfterCancelMs); + + public Task> InvokeAsync (CancellationToken token) + { + if (Task != null) throw new Exception("Task must be null"); + + token.Register (() => { + try { + CancelImpl (); + } + catch (OperationCanceledException) { + // if CancelImpl throw OCE we shouldn't mute it + throw; + } + catch (Exception e) { + DebuggerLoggingService.LogMessage ("Exception in CancelImpl(): {0}", e.Message); + } + }); + Task = InvokeAsyncImpl (token); + return Task; + } + protected abstract Task> InvokeAsyncImpl (CancellationToken token); + + protected abstract void CancelImpl (); + + } +} \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index 05c965c76..5db1385ee 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -27,215 +27,172 @@ using System; using System.Collections.Generic; -using ST = System.Threading; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Mono.Debugging.Client; namespace Mono.Debugging.Evaluation { - public class AsyncOperationManager: IDisposable + public class AsyncOperationManager : IDisposable { - readonly List operationsToCancel = new List (); - readonly object operationsSync = new object (); + class OperationData + { + public IAsyncOperationBase Operation { get; private set; } + public CancellationTokenSource TokenSource { get; private set; } + + public OperationData (IAsyncOperationBase operation, CancellationTokenSource tokenSource) + { + Operation = operation; + TokenSource = tokenSource; + } + } + + readonly HashSet currentOperations = new HashSet (); + bool disposed = false; + const int ShortCancelTimeout = 100; + const int LongCancelTimeout = 1000; + + static bool IsOperationCancelledException (Exception e, int depth = 4) + { + if (e is OperationCanceledException) + return true; + var aggregateException = e as AggregateException; - internal bool Disposing; + if (depth > 0 && aggregateException != null) { + foreach (var innerException in aggregateException.InnerExceptions) { + if (IsOperationCancelledException (innerException, depth - 1)) + return true; + } + } + return false; + } - public void Invoke (AsyncOperation methodCall, int timeout) + public OperationResult Invoke (AsyncOperationBase mc, int timeout) { - methodCall.Aborted = false; - methodCall.Manager = this; + if (timeout <= 0) + throw new ArgumentOutOfRangeException("timeout", timeout, "timeout must be greater than 0"); - lock (operationsSync) { - operationsToCancel.Add (methodCall); - methodCall.Invoke (); + Task> task; + var description = mc.Description; + var cts = new CancellationTokenSource (); + var operationData = new OperationData (mc, cts); + lock (currentOperations) { + if (disposed) + throw new ObjectDisposedException ("Already disposed"); + DebuggerLoggingService.LogMessage (string.Format("Starting invoke for {0}", description)); + task = mc.InvokeAsync (cts.Token); + currentOperations.Add (operationData); } - if (timeout > 0) { - if (!methodCall.WaitForCompleted (timeout)) { - bool wasAborted = methodCall.Aborted; - methodCall.InternalAbort (); - lock (operationsSync) { - operationsToCancel.Remove (methodCall); + bool cancelledAfterTimeout = false; + try { + if (task.Wait (timeout)) { + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} succeeded in {1} ms", description, timeout)); + return task.Result; + } + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} timed out after {1} ms. Cancelling.", description, timeout)); + cts.Cancel (); + try { + WaitAfterCancel (mc); + } + catch (Exception e) { + if (IsOperationCancelledException (e)) { + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled after timeout", description)); + cancelledAfterTimeout = true; } - if (wasAborted) - throw new EvaluatorAbortedException (); - else - throw new TimeOutException (); + throw; } + DebuggerLoggingService.LogMessage (string.Format ("{0} cancelling timed out", description)); + throw new TimeOutException (); } - else { - methodCall.WaitForCompleted (System.Threading.Timeout.Infinite); - } - - lock (operationsSync) { - operationsToCancel.Remove (methodCall); - if (methodCall.Aborted) { + catch (Exception e) { + if (IsOperationCancelledException (e)) { + if (cancelledAfterTimeout) + throw new TimeOutException (); + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled outside before timeout", description)); throw new EvaluatorAbortedException (); } + throw; } - - if (!string.IsNullOrEmpty (methodCall.ExceptionMessage)) { - throw new Exception (methodCall.ExceptionMessage); + finally { + lock (currentOperations) { + currentOperations.Remove (operationData); + } } } - - public void Dispose () + + + public event EventHandler BusyStateChanged = delegate { }; + + void WaitAfterCancel (IAsyncOperationBase op) { - Disposing = true; - lock (operationsSync) { - foreach (AsyncOperation op in operationsToCancel) { - op.InternalShutdown (); + var desc = op.Description; + DebuggerLoggingService.LogMessage (string.Format ("Waiting for cancel of invoke {0}", desc)); + try { + if (!op.RawTask.Wait (ShortCancelTimeout)) { + try { + BusyStateChanged (this, new BusyStateEventArgs {IsBusy = true, Description = desc}); + op.RawTask.Wait (LongCancelTimeout); + } + finally { + BusyStateChanged (this, new BusyStateEventArgs {IsBusy = false, Description = desc}); + } } - operationsToCancel.Clear (); + } + finally { + DebuggerLoggingService.LogMessage (string.Format ("Calling AfterCancelled() for {0}", desc)); + op.AfterCancelled (ShortCancelTimeout + LongCancelTimeout); } } + public void AbortAll () { - lock (operationsSync) { - foreach (AsyncOperation op in operationsToCancel) - op.InternalAbort (); + DebuggerLoggingService.LogMessage ("Aborting all the current invocations"); + List copy; + lock (currentOperations) { + if (disposed) throw new ObjectDisposedException ("Already disposed"); + copy = currentOperations.ToList (); + currentOperations.Clear (); } + + CancelOperations (copy, true); } - - public void EnterBusyState (AsyncOperation oper) - { - BusyStateEventArgs args = new BusyStateEventArgs (); - args.IsBusy = true; - args.Description = oper.Description; - if (BusyStateChanged != null) - BusyStateChanged (this, args); - } - - public void LeaveBusyState (AsyncOperation oper) - { - BusyStateEventArgs args = new BusyStateEventArgs (); - args.IsBusy = false; - args.Description = oper.Description; - if (BusyStateChanged != null) - BusyStateChanged (this, args); - } - - public event EventHandler BusyStateChanged; - } - public abstract class AsyncOperation - { - internal bool Aborted; - internal AsyncOperationManager Manager; - - public bool Aborting { get; internal set; } - - internal void InternalAbort () + void CancelOperations (List operations, bool wait) { - ST.Monitor.Enter (this); - if (Aborted) { - ST.Monitor.Exit (this); - return; - } - - if (Aborting) { - // Somebody else is aborting this. Just wait for it to finish. - ST.Monitor.Exit (this); - WaitForCompleted (ST.Timeout.Infinite); - return; - } - - Aborting = true; - - int abortState = 0; - int abortRetryWait = 100; - bool abortRequested = false; - - do { - if (abortState > 0) - ST.Monitor.Enter (this); - + foreach (var operationData in operations) { + var taskDescription = operationData.Operation.Description; try { - if (!Aborted && !abortRequested) { - // The Abort() call doesn't block. WaitForCompleted is used below to wait for the abort to succeed - Abort (); - abortRequested = true; - } - // Short wait for the Abort to finish. If this wait is not enough, it will wait again in the next loop - if (WaitForCompleted (100)) { - ST.Monitor.Exit (this); - break; + operationData.TokenSource.Cancel (); + if (wait) { + WaitAfterCancel (operationData.Operation); } - } catch { - // If abort fails, try again after a short wait } - abortState++; - if (abortState == 6) { - // Several abort calls have failed. Inform the user that the debugger is busy - abortRetryWait = 500; - try { - Manager.EnterBusyState (this); - } catch (Exception ex) { - Console.WriteLine (ex); + catch (Exception e) { + if (IsOperationCancelledException (e)) { + DebuggerLoggingService.LogMessage (string.Format ("Invocation of {0} cancelled in CancelOperations()", taskDescription)); + } + else { + DebuggerLoggingService.LogError (string.Format ("Invocation of {0} thrown an exception in CancelOperations()", taskDescription), e); } - } - ST.Monitor.Exit (this); - } while (!Aborted && !WaitForCompleted (abortRetryWait) && !Manager.Disposing); - - if (Manager.Disposing) { - InternalShutdown (); - } - else { - lock (this) { - Aborted = true; - if (abortState >= 6) - Manager.LeaveBusyState (this); } } } - - internal void InternalShutdown () + + + public void Dispose () { - lock (this) { - if (Aborted) - return; - try { - Aborted = true; - Shutdown (); - } catch { - // Ignore - } + List copy; + lock (currentOperations) { + if (disposed) throw new ObjectDisposedException ("Already disposed"); + disposed = true; + copy = currentOperations.ToList (); + currentOperations.Clear (); } + // don't wait on dispose + CancelOperations (copy, wait: false); } - - /// - /// Message of the exception, if the execution failed. - /// - public string ExceptionMessage { get; set; } - - /// - /// Returns a short description of the operation, to be shown in the Debugger Busy Dialog - /// when it blocks the execution of the debugger. - /// - public abstract string Description { get; } - - /// - /// Called to invoke the operation. The execution must be asynchronous (it must return immediatelly). - /// - public abstract void Invoke ( ); - - /// - /// Called to abort the execution of the operation. It has to throw an exception - /// if the operation can't be aborted. This operation must not block. The engine - /// will wait for the operation to be aborted by calling WaitForCompleted. - /// - public abstract void Abort (); - - /// - /// Waits until the operation has been completed or aborted. - /// - public abstract bool WaitForCompleted (int timeout); - - /// - /// Called when the debugging session has been disposed. - /// I must cause any call to WaitForCompleted to exit, even if the operation - /// has not been completed or can't be aborted. - /// - public abstract void Shutdown (); } -} +} \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs b/Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs new file mode 100644 index 000000000..73842e20f --- /dev/null +++ b/Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs @@ -0,0 +1,22 @@ +namespace Mono.Debugging.Evaluation +{ + public interface IAsyncOperation + { + /// + /// Called to invoke the operation. The execution must be asynchronous (it must return immediatelly). + /// + void BeginInvoke (); + + /// + /// Called to abort the execution of the operation. It has to throw an exception + /// if the operation can't be aborted. This operation must not block. The engine + /// will wait for the operation to be aborted by calling WaitForCompleted. + /// + void Abort (); + + /// + /// Waits until the operation has been completed or aborted. + /// + bool WaitForCompleted (int timeout); + } +} \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs index 6afbfd479..2ca1d34de 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs @@ -1328,9 +1328,9 @@ public string EvaluateDisplayString (EvaluationContext ctx, object obj, string e return display.ToString (); } - public void AsyncExecute (AsyncOperation operation, int timeout) + public OperationResult InvokeSync (AsyncOperationBase operation, int timeout) { - asyncOperationManager.Invoke (operation, timeout); + return asyncOperationManager.Invoke (operation, timeout); } public ObjectValue CreateObjectValueAsync (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator) @@ -1364,7 +1364,7 @@ public ObjectValue GetExpressionValue (EvaluationContext ctx, string exp) } catch (EvaluatorException ex) { return ObjectValue.CreateError (ctx.ExpressionValueSource, new ObjectPath (exp), "", ex.Message, ObjectValueFlags.None); } catch (Exception ex) { - ctx.WriteDebuggerError (ex); + DebuggerLoggingService.LogError ("Exception in GetExpressionValue()", ex); return ObjectValue.CreateUnknown (exp); } } diff --git a/Mono.Debugging/Mono.Debugging.csproj b/Mono.Debugging/Mono.Debugging.csproj index 1b1ff538b..abeb836b3 100644 --- a/Mono.Debugging/Mono.Debugging.csproj +++ b/Mono.Debugging/Mono.Debugging.csproj @@ -82,10 +82,12 @@ + + From ad8187ff1eae40329eee65d803ba4c4e6d1fd67e Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Tue, 22 Nov 2016 22:30:13 +0300 Subject: [PATCH 06/21] Proper logging: before the exception body was lost, now it's logged. --- Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs | 3 +-- .../Mono.Debugging.Evaluation/ObjectValueAdaptor.cs | 2 +- Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs b/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs index 13055da1a..366150b21 100644 --- a/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs +++ b/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs @@ -541,8 +541,7 @@ public ObjectValue[] GetAllChildren (EvaluationOptions options) ConnectCallbacks (parentFrame, cs); children.AddRange (cs); } catch (Exception ex) { - if (parentFrame != null) - parentFrame.DebuggerSession.OnDebuggerOutput (true, ex.ToString ()); + DebuggerLoggingService.LogError ("Exception in GetAllChildren()", ex); children.Add (CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly)); } } diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs index 2ca1d34de..f2cfafa8b 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ObjectValueAdaptor.cs @@ -590,7 +590,7 @@ public virtual ObjectValue[] GetObjectValueChildren (EvaluationContext ctx, IObj values.Add (oval); } } catch (Exception ex) { - ctx.WriteDebuggerError (ex); + DebuggerLoggingService.LogError ("Exception in GetObjectValueChildren()", ex); values.Add (ObjectValue.CreateError (null, new ObjectPath (val.Name), GetDisplayTypeName (GetTypeName (ctx, val.Type)), ex.Message, val.Flags)); } } diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs index 9e23167c1..4bcb58b69 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs @@ -110,8 +110,9 @@ public ObjectValue CreateObjectValue (EvaluationOptions options) return DC.ObjectValue.CreateEvaluationException (Context, Context.ExpressionValueSource, new ObjectPath (Name), ex); } catch (EvaluatorException ex) { return DC.ObjectValue.CreateError (this, new ObjectPath (Name), "", ex.Message, Flags); - } catch (Exception ex) { - Context.WriteDebuggerError (ex); + } + catch (Exception ex) { + DebuggerLoggingService.LogError ("Exception in CreateObjectValue()", ex); return DC.ObjectValue.CreateUnknown (Name); } } From 5be3b5b4c627ab6a6128720074dc5e5f254daf36 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Tue, 17 Jan 2017 17:47:19 +0300 Subject: [PATCH 07/21] Invocation is awaited infinitely as it was before. Better handling of exeptions in Soft invocations --- Mono.Debugging.Soft/SoftDebuggerAdaptor.cs | 23 ++++++++++++++++++- .../AsyncOperationManager.cs | 19 +++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs index 21fd95142..74ebd9453 100644 --- a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs +++ b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs @@ -2212,8 +2212,29 @@ protected override Task> InvokeAsyncImpl (CancellationTok tcs.SetException (new EvaluatorException ("Target method has thrown an exception but the exception object is inaccessible")); } } + catch (CommandException e) { + if (e.ErrorCode == ErrorCode.INVOKE_ABORTED) { + tcs.TrySetCanceled (); + token.ThrowIfCancellationRequested (); + } + else { + tcs.SetException (new EvaluatorException (e.Message)); + } + } catch (Exception e) { - tcs.SetException (e); + if (e is ObjectCollectedException || + e is InvalidStackFrameException || + e is VMNotSuspendedException || + e is NotSupportedException || + e is AbsentInformationException || + e is ArgumentException) { + // user meaningfull evaluation exception -> wrap with EvaluatorException that will be properly shown in value viewer + tcs.SetException (new EvaluatorException (e.Message)); + } + else { + DebuggerLoggingService.LogError ("Unexpected exception has thrown in Invocation", e); + tcs.SetException (e); + } } finally { UpdateSessionState (); diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index 5db1385ee..a73ec765f 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -51,7 +51,6 @@ public OperationData (IAsyncOperationBase operation, CancellationTokenSource tok readonly HashSet currentOperations = new HashSet (); bool disposed = false; const int ShortCancelTimeout = 100; - const int LongCancelTimeout = 1000; static bool IsOperationCancelledException (Exception e, int depth = 4) { @@ -125,6 +124,16 @@ public OperationResult Invoke (AsyncOperationBase mc, in public event EventHandler BusyStateChanged = delegate { }; + void ChangeBusyState (bool busy, string description) + { + try { + BusyStateChanged (this, new BusyStateEventArgs {IsBusy = true, Description = description}); + } + catch (Exception e) { + DebuggerLoggingService.LogError ("Exception during ChangeBusyState", e); + } + } + void WaitAfterCancel (IAsyncOperationBase op) { var desc = op.Description; @@ -132,17 +141,17 @@ void WaitAfterCancel (IAsyncOperationBase op) try { if (!op.RawTask.Wait (ShortCancelTimeout)) { try { - BusyStateChanged (this, new BusyStateEventArgs {IsBusy = true, Description = desc}); - op.RawTask.Wait (LongCancelTimeout); + ChangeBusyState (true, desc); + op.RawTask.Wait (Timeout.Infinite); } finally { - BusyStateChanged (this, new BusyStateEventArgs {IsBusy = false, Description = desc}); + ChangeBusyState (false, desc); } } } finally { DebuggerLoggingService.LogMessage (string.Format ("Calling AfterCancelled() for {0}", desc)); - op.AfterCancelled (ShortCancelTimeout + LongCancelTimeout); + op.AfterCancelled (ShortCancelTimeout); } } From 1d4a9c4f669ec6d1a83aa0b39cb2a7bce9312f1d Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Tue, 17 Jan 2017 18:22:43 +0300 Subject: [PATCH 08/21] Restore GetInfo() and detailed logging on invocation --- Mono.Debugging.Soft/SoftDebuggerAdaptor.cs | 35 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs index 74ebd9453..a5f1802d0 100644 --- a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs +++ b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs @@ -2179,8 +2179,15 @@ public SoftMethodCall (SoftEvaluationContext ctx, MethodMirror function, IInvoca } public override string Description { - get { - return function.DeclaringType.FullName + "." + function.Name; + get + { + try { + return function.DeclaringType.FullName + "." + function.Name; + } + catch (Exception e) { + DebuggerLoggingService.LogError ("Exception during getting description of method", e); + return "[Unknown method]"; + } } } @@ -2232,7 +2239,7 @@ e is AbsentInformationException || tcs.SetException (new EvaluatorException (e.Message)); } else { - DebuggerLoggingService.LogError ("Unexpected exception has thrown in Invocation", e); + DebuggerLoggingService.LogError (string.Format ("Unexpected exception has thrown when ending invocation of {0}", GetInfo ()), e); tcs.SetException (e); } } @@ -2241,12 +2248,32 @@ e is AbsentInformationException || } }, null); return tcs.Task; - } catch (Exception) { + } catch (Exception e) { UpdateSessionState (); + DebuggerLoggingService.LogError (string.Format ("Unexpected exception has thrown when invoking {0}", GetInfo ()), e); throw; } } + string GetInfo () + { + try { + TypeMirror type = null; + if (obj is ObjectMirror) + type = ((ObjectMirror)obj).Type; + else if (obj is TypeMirror) + type = (TypeMirror)obj; + else if (obj is StructMirror) + type = ((StructMirror)obj).Type; + return string.Format ("method {0} on object {1}", + function.FullName, + type == null? "[null]" : type.FullName); + } catch (Exception ex) { + DebuggerLoggingService.LogError ("Error getting info for SDB MethodCall", ex); + return "[Unknown method]"; + } + } + void UpdateSessionState () { ctx.Session.StackVersion++; From d8ef7b81001bcd55d3144f0dbe053fd244a3aeb0 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Mon, 23 Jan 2017 22:38:16 +0300 Subject: [PATCH 09/21] More proper evaluation aborting. --- Mono.Debugging.Soft/SoftDebuggerAdaptor.cs | 17 +++--- .../AsyncOperationBase.cs | 52 +++++++++++-------- .../AsyncOperationManager.cs | 40 ++++++-------- 3 files changed, 51 insertions(+), 58 deletions(-) diff --git a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs index a5f1802d0..bb1c991ea 100644 --- a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs +++ b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs @@ -32,7 +32,6 @@ using System.Reflection.Emit; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; using Mono.Debugger.Soft; using Mono.Debugging.Backend; @@ -2191,11 +2190,7 @@ public override string Description { } } - protected override void AfterCancelledImpl (int elapsedAfterCancelMs) - { - } - - protected override Task> InvokeAsyncImpl (CancellationToken token) + protected override Task> InvokeAsyncImpl () { try { var optionsToInvoke = options; @@ -2204,14 +2199,15 @@ protected override Task> InvokeAsyncImpl (CancellationTok } var tcs = new TaskCompletionSource> (); invokeAsyncResult = (IInvokeAsyncResult)obj.BeginInvokeMethod (ctx.Thread, function, args, optionsToInvoke, ar => { + if (Token.IsCancellationRequested) { + tcs.SetCanceled (); + return; + } try { var endInvokeResult = obj.EndInvokeMethodWithResult (ar); - token.ThrowIfCancellationRequested (); tcs.SetResult (new SoftOperationResult (endInvokeResult.Result, false, endInvokeResult.OutArgs)); } catch (InvocationException ex) { - // throw OCE if cancelled - token.ThrowIfCancellationRequested (); if (ex.Exception != null) { tcs.SetResult (new SoftOperationResult (ex.Exception, true, null)); } @@ -2222,7 +2218,6 @@ protected override Task> InvokeAsyncImpl (CancellationTok catch (CommandException e) { if (e.ErrorCode == ErrorCode.INVOKE_ABORTED) { tcs.TrySetCanceled (); - token.ThrowIfCancellationRequested (); } else { tcs.SetException (new EvaluatorException (e.Message)); @@ -2279,7 +2274,7 @@ void UpdateSessionState () ctx.Session.StackVersion++; } - protected override void CancelImpl () + protected override void AbortImpl (int abortCallTimes) { if (invokeAsyncResult == null) { DebuggerLoggingService.LogError ("invokeAsyncResult is null", new ArgumentNullException ("invokeAsyncResult")); diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs index 62fe8c90b..25b09c880 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs @@ -32,7 +32,7 @@ public interface IAsyncOperationBase { Task RawTask { get; } string Description { get; } - void AfterCancelled (int elapsedAfterCancelMs); + void Abort (); } public abstract class AsyncOperationBase : IAsyncOperationBase @@ -49,40 +49,46 @@ public Task RawTask public abstract string Description { get; } - public void AfterCancelled (int elapsedAfterCancelMs) + int abortCalls = 0; + + readonly CancellationTokenSource tokenSource = new CancellationTokenSource (); + + /// + /// When evaluation is aborted and debugger callback is invoked the implementation has to check + /// for Token.IsCancellationRequested and call Task.SetCancelled() instead of setting the result + /// + protected CancellationToken Token { get { return tokenSource.Token; } } + + public void Abort () { try { - AfterCancelledImpl (elapsedAfterCancelMs); + tokenSource.Cancel(); + AbortImpl (Interlocked.Increment (ref abortCalls) - 1); + } + catch (OperationCanceledException) { + // if CancelImpl throw OCE we shouldn't mute it + throw; } catch (Exception e) { - DebuggerLoggingService.LogError ("AfterCancelledImpl() thrown an exception", e); + DebuggerLoggingService.LogMessage ("Exception in CancelImpl(): {0}", e.Message); } } - protected abstract void AfterCancelledImpl (int elapsedAfterCancelMs); - - public Task> InvokeAsync (CancellationToken token) + public Task> InvokeAsync () { if (Task != null) throw new Exception("Task must be null"); - - token.Register (() => { - try { - CancelImpl (); - } - catch (OperationCanceledException) { - // if CancelImpl throw OCE we shouldn't mute it - throw; - } - catch (Exception e) { - DebuggerLoggingService.LogMessage ("Exception in CancelImpl(): {0}", e.Message); - } - }); - Task = InvokeAsyncImpl (token); + Task = InvokeAsyncImpl (); return Task; } - protected abstract Task> InvokeAsyncImpl (CancellationToken token); - protected abstract void CancelImpl (); + protected abstract Task> InvokeAsyncImpl (); + + /// + /// The implementation has to tell the debugger to abort the evaluation. This method must bot block. + /// + /// indicates how many times this method has been already called for this evaluation. + /// E.g. the implementation can perform some 'rude abort' after several previous ordinary 'aborts' were failed. For the first call this parameter == 0 + protected abstract void AbortImpl (int abortCallTimes); } } \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index a73ec765f..f2124acdc 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -28,7 +28,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Mono.Debugging.Client; @@ -39,12 +38,9 @@ public class AsyncOperationManager : IDisposable class OperationData { public IAsyncOperationBase Operation { get; private set; } - public CancellationTokenSource TokenSource { get; private set; } - - public OperationData (IAsyncOperationBase operation, CancellationTokenSource tokenSource) + public OperationData (IAsyncOperationBase operation) { Operation = operation; - TokenSource = tokenSource; } } @@ -74,13 +70,12 @@ public OperationResult Invoke (AsyncOperationBase mc, in Task> task; var description = mc.Description; - var cts = new CancellationTokenSource (); - var operationData = new OperationData (mc, cts); + var operationData = new OperationData (mc); lock (currentOperations) { if (disposed) throw new ObjectDisposedException ("Already disposed"); DebuggerLoggingService.LogMessage (string.Format("Starting invoke for {0}", description)); - task = mc.InvokeAsync (cts.Token); + task = mc.InvokeAsync (); currentOperations.Add (operationData); } @@ -91,7 +86,7 @@ public OperationResult Invoke (AsyncOperationBase mc, in return task.Result; } DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} timed out after {1} ms. Cancelling.", description, timeout)); - cts.Cancel (); + mc.Abort (); try { WaitAfterCancel (mc); } @@ -127,7 +122,7 @@ public OperationResult Invoke (AsyncOperationBase mc, in void ChangeBusyState (bool busy, string description) { try { - BusyStateChanged (this, new BusyStateEventArgs {IsBusy = true, Description = description}); + BusyStateChanged (this, new BusyStateEventArgs {IsBusy = busy, Description = description}); } catch (Exception e) { DebuggerLoggingService.LogError ("Exception during ChangeBusyState", e); @@ -138,24 +133,21 @@ void WaitAfterCancel (IAsyncOperationBase op) { var desc = op.Description; DebuggerLoggingService.LogMessage (string.Format ("Waiting for cancel of invoke {0}", desc)); - try { - if (!op.RawTask.Wait (ShortCancelTimeout)) { - try { - ChangeBusyState (true, desc); - op.RawTask.Wait (Timeout.Infinite); - } - finally { - ChangeBusyState (false, desc); + if (!op.RawTask.Wait (ShortCancelTimeout)) { + try { + ChangeBusyState (true, desc); + while (true) { + op.Abort (); + if (op.RawTask.Wait (ShortCancelTimeout)) + break; } } - } - finally { - DebuggerLoggingService.LogMessage (string.Format ("Calling AfterCancelled() for {0}", desc)); - op.AfterCancelled (ShortCancelTimeout); + finally { + ChangeBusyState (false, desc); + } } } - public void AbortAll () { DebuggerLoggingService.LogMessage ("Aborting all the current invocations"); @@ -174,7 +166,7 @@ void CancelOperations (List operations, bool wait) foreach (var operationData in operations) { var taskDescription = operationData.Operation.Description; try { - operationData.TokenSource.Cancel (); + operationData.Operation.Abort (); if (wait) { WaitAfterCancel (operationData.Operation); } From 3226be3c31f8c813f0cae58cef15fa63722c912c Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Mon, 23 Jan 2017 22:41:31 +0300 Subject: [PATCH 10/21] Get rid of OperationData --- .../AsyncOperationManager.cs | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index f2124acdc..c603ff227 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -35,16 +35,7 @@ namespace Mono.Debugging.Evaluation { public class AsyncOperationManager : IDisposable { - class OperationData - { - public IAsyncOperationBase Operation { get; private set; } - public OperationData (IAsyncOperationBase operation) - { - Operation = operation; - } - } - - readonly HashSet currentOperations = new HashSet (); + readonly HashSet currentOperations = new HashSet (); bool disposed = false; const int ShortCancelTimeout = 100; @@ -70,13 +61,12 @@ public OperationResult Invoke (AsyncOperationBase mc, in Task> task; var description = mc.Description; - var operationData = new OperationData (mc); lock (currentOperations) { if (disposed) throw new ObjectDisposedException ("Already disposed"); DebuggerLoggingService.LogMessage (string.Format("Starting invoke for {0}", description)); task = mc.InvokeAsync (); - currentOperations.Add (operationData); + currentOperations.Add (mc); } bool cancelledAfterTimeout = false; @@ -111,7 +101,7 @@ public OperationResult Invoke (AsyncOperationBase mc, in } finally { lock (currentOperations) { - currentOperations.Remove (operationData); + currentOperations.Remove (mc); } } } @@ -151,7 +141,7 @@ void WaitAfterCancel (IAsyncOperationBase op) public void AbortAll () { DebuggerLoggingService.LogMessage ("Aborting all the current invocations"); - List copy; + List copy; lock (currentOperations) { if (disposed) throw new ObjectDisposedException ("Already disposed"); copy = currentOperations.ToList (); @@ -161,14 +151,14 @@ public void AbortAll () CancelOperations (copy, true); } - void CancelOperations (List operations, bool wait) + void CancelOperations (List operations, bool wait) { - foreach (var operationData in operations) { - var taskDescription = operationData.Operation.Description; + foreach (var operation in operations) { + var taskDescription = operation.Description; try { - operationData.Operation.Abort (); + operation.Abort (); if (wait) { - WaitAfterCancel (operationData.Operation); + WaitAfterCancel (operation); } } catch (Exception e) { @@ -185,7 +175,7 @@ void CancelOperations (List operations, bool wait) public void Dispose () { - List copy; + List copy; lock (currentOperations) { if (disposed) throw new ObjectDisposedException ("Already disposed"); disposed = true; From e9ee3d0eab6f254220b4c38e200166d2ad52443b Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Wed, 1 Feb 2017 23:12:58 +0300 Subject: [PATCH 11/21] Move checking for cancelled token into try-catch to guarantee that UpdateSessionState() is invoked (in finally) (cherry picked from commit 3befe19) --- Mono.Debugging.Soft/SoftDebuggerAdaptor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs index bb1c991ea..7369f87c1 100644 --- a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs +++ b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs @@ -2199,11 +2199,11 @@ protected override Task> InvokeAsyncImpl () } var tcs = new TaskCompletionSource> (); invokeAsyncResult = (IInvokeAsyncResult)obj.BeginInvokeMethod (ctx.Thread, function, args, optionsToInvoke, ar => { - if (Token.IsCancellationRequested) { - tcs.SetCanceled (); - return; - } try { + if (Token.IsCancellationRequested) { + tcs.SetCanceled (); + return; + } var endInvokeResult = obj.EndInvokeMethodWithResult (ar); tcs.SetResult (new SoftOperationResult (endInvokeResult.Result, false, endInvokeResult.OutArgs)); } From 5db72cd646e0c241a7b5d65eccd618d27eb09e40 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Thu, 16 Mar 2017 21:55:08 +0300 Subject: [PATCH 12/21] CorDebug Invocations rewritten to .Net Task API (commit moved from MD repo) --- Mono.Debugging.Win32/CorDebuggerSession.cs | 83 ++++------------ Mono.Debugging.Win32/CorMethodCall.cs | 106 +++++++++++++++++---- 2 files changed, 105 insertions(+), 84 deletions(-) diff --git a/Mono.Debugging.Win32/CorDebuggerSession.cs b/Mono.Debugging.Win32/CorDebuggerSession.cs index d55d1819a..69bffdda1 100644 --- a/Mono.Debugging.Win32/CorDebuggerSession.cs +++ b/Mono.Debugging.Win32/CorDebuggerSession.cs @@ -111,6 +111,13 @@ public static int EvaluationTimestamp { get { return evaluationTimestamp; } } + internal CorProcess Process + { + get + { + return process; + } + } public override void Dispose ( ) { @@ -1417,49 +1424,16 @@ public CorValue RuntimeInvoke (CorEvaluationContext ctx, CorFunction function, C arguments.CopyTo (args, 1); } - CorMethodCall mc = new CorMethodCall (); - CorValue exception = null; - CorEval eval = ctx.Eval; - - DebugEventHandler completeHandler = delegate (object o, CorEvalEventArgs eargs) { - OnEndEvaluating (); - mc.DoneEvent.Set (); - eargs.Continue = false; - }; - - DebugEventHandler exceptionHandler = delegate(object o, CorEvalEventArgs eargs) { - OnEndEvaluating (); - exception = eargs.Eval.Result; - mc.DoneEvent.Set (); - eargs.Continue = false; - }; - - process.OnEvalComplete += completeHandler; - process.OnEvalException += exceptionHandler; - - mc.OnInvoke = delegate { - if (function.GetMethodInfo (this).Name == ".ctor") - eval.NewParameterizedObject (function, typeArgs, args); - else - eval.CallParameterizedFunction (function, typeArgs, args); - process.SetAllThreadsDebugState (CorDebugThreadState.THREAD_SUSPEND, ctx.Thread); - ClearEvalStatus (); - OnStartEvaluating (); - process.Continue (false); - }; - mc.OnAbort = delegate { - eval.Abort (); - }; - mc.OnGetDescription = delegate { - MethodInfo met = function.GetMethodInfo (ctx.Session); - if (met != null) - return met.DeclaringType.FullName + "." + met.Name; - else - return ""; - }; - + var methodCall = new CorMethodCall (ctx, function, typeArgs, args); try { - ObjectAdapter.AsyncExecute (mc, ctx.Options.EvaluationTimeout); + var result = ObjectAdapter.InvokeSync (methodCall, ctx.Options.EvaluationTimeout); + if (result.ResultIsException) { + var vref = new CorValRef (result.Result); + throw new EvaluatorExceptionThrownException (vref, ObjectAdapter.GetValueTypeName (ctx, vref)); + } + + WaitUntilStopped (); + return result.Result; } catch (COMException ex) { // eval exception is a 'good' exception that should be shown in value box @@ -1469,35 +1443,16 @@ public CorValue RuntimeInvoke (CorEvaluationContext ctx, CorFunction function, C throw evalException; throw; } - finally { - process.OnEvalComplete -= completeHandler; - process.OnEvalException -= exceptionHandler; - } - - WaitUntilStopped (); - if (exception != null) { -/* ValueReference msg = ctx.Adapter.GetMember (ctx, val, "Message"); - if (msg != null) { - string s = msg.ObjectValue as string; - mc.ExceptionMessage = s; - } - else - mc.ExceptionMessage = "Evaluation failed.";*/ - CorValRef vref = new CorValRef (exception); - throw new EvaluatorException ("Evaluation failed: " + ObjectAdapter.GetValueTypeName (ctx, vref)); - } - - return eval.Result; } - void OnStartEvaluating ( ) + internal void OnStartEvaluating ( ) { lock (debugLock) { evaluating = true; } } - void OnEndEvaluating ( ) + internal void OnEndEvaluating ( ) { lock (debugLock) { evaluating = false; @@ -1603,7 +1558,7 @@ public void WaitUntilStopped () } } - void ClearEvalStatus ( ) + internal void ClearEvalStatus ( ) { foreach (CorProcess p in dbg.Processes) { if (p.Id == processId) { diff --git a/Mono.Debugging.Win32/CorMethodCall.cs b/Mono.Debugging.Win32/CorMethodCall.cs index 08637eb49..f3d98c9f9 100644 --- a/Mono.Debugging.Win32/CorMethodCall.cs +++ b/Mono.Debugging.Win32/CorMethodCall.cs @@ -1,47 +1,113 @@ using System.Threading; +using System.Threading.Tasks; +using Microsoft.Samples.Debugging.CorDebug; +using Microsoft.Samples.Debugging.CorDebug.NativeApi; using Mono.Debugging.Evaluation; namespace Mono.Debugging.Win32 { - class CorMethodCall: AsyncOperation + class CorMethodCall: AsyncOperationBase { - public delegate void CallCallback ( ); - public delegate string DescriptionCallback ( ); + readonly CorEvaluationContext context; + readonly CorFunction function; + readonly CorType[] typeArgs; + readonly CorValue[] args; - public CallCallback OnInvoke; - public CallCallback OnAbort; - public DescriptionCallback OnGetDescription; + readonly CorEval eval; - public ManualResetEvent DoneEvent = new ManualResetEvent (false); + public CorMethodCall (CorEvaluationContext context, CorFunction function, CorType[] typeArgs, CorValue[] args) + { + this.context = context; + this.function = function; + this.typeArgs = typeArgs; + this.args = args; + eval = context.Eval; + } - public override string Description + void ProcessOnEvalComplete (object sender, CorEvalEventArgs evalArgs) + { + DoProcessEvalFinished (evalArgs, false); + } + + void ProcessOnEvalException (object sender, CorEvalEventArgs evalArgs) { - get { return OnGetDescription (); } + DoProcessEvalFinished (evalArgs, true); } - public override void Invoke ( ) + void DoProcessEvalFinished (CorEvalEventArgs evalArgs, bool isException) { - OnInvoke (); + if (evalArgs.Eval != eval) + return; + context.Session.OnEndEvaluating (); + evalArgs.Continue = false; + tcs.TrySetResult(new OperationResult (evalArgs.Eval.Result, isException)); } - public override void Abort ( ) + void SubscribeOnEvals () { - OnAbort (); + context.Session.Process.OnEvalComplete += ProcessOnEvalComplete; + context.Session.Process.OnEvalException += ProcessOnEvalException; } - public override void Shutdown ( ) + void UnSubcribeOnEvals () + { + context.Session.Process.OnEvalComplete -= ProcessOnEvalComplete; + context.Session.Process.OnEvalException -= ProcessOnEvalException; + } + + public override string Description { - try { - Abort (); + get + { + var met = function.GetMethodInfo (context.Session); + if (met == null) + return ""; + if (met.DeclaringType == null) + return met.Name; + return met.DeclaringType.FullName + "." + met.Name; } - catch { + } + + readonly TaskCompletionSource> tcs = new TaskCompletionSource> (); + const int DelayAfterAbort = 500; + + protected override void AfterCancelledImpl (int elapsedAfterCancelMs) + { + if (tcs.TrySetCanceled ()) { + // really cancelled for the first time not before. so we should check that we awaited necessary amout of time after Abort() call + // else if we return too earle after Abort() the process may be PROCESS_NOT_SYNCHRONIZED + if (elapsedAfterCancelMs < DelayAfterAbort) { + Thread.Sleep (DelayAfterAbort - elapsedAfterCancelMs); + } } - DoneEvent.Set (); + context.Session.OnEndEvaluating (); } - public override bool WaitForCompleted (int timeout) + protected override Task> InvokeAsyncImpl (CancellationToken token) + { + SubscribeOnEvals (); + + if (function.GetMethodInfo (context.Session).Name == ".ctor") + eval.NewParameterizedObject (function, typeArgs, args); + else + eval.CallParameterizedFunction (function, typeArgs, args); + context.Session.Process.SetAllThreadsDebugState (CorDebugThreadState.THREAD_SUSPEND, context.Thread); + context.Session.ClearEvalStatus (); + context.Session.OnStartEvaluating (); + context.Session.Process.Continue (false); + Task = tcs.Task; + // Don't pass token here, because it causes immediately task cancellation which must be performed by debugger event or real timeout + // ReSharper disable once MethodSupportsCancellation + return Task.ContinueWith (task => { + UnSubcribeOnEvals (); + return task.Result; + }); + } + + + protected override void CancelImpl ( ) { - return DoneEvent.WaitOne (timeout, false); + eval.Abort (); } } } From 0aec667748ad1f2dc1791948e5bade980b84c4e2 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Thu, 16 Mar 2017 21:56:56 +0300 Subject: [PATCH 13/21] CorDebug Checking for aborted state in CorMethodCall --- Mono.Debugging.Win32/CorMethodCall.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Mono.Debugging.Win32/CorMethodCall.cs b/Mono.Debugging.Win32/CorMethodCall.cs index f3d98c9f9..82a0d0f7b 100644 --- a/Mono.Debugging.Win32/CorMethodCall.cs +++ b/Mono.Debugging.Win32/CorMethodCall.cs @@ -40,7 +40,12 @@ void DoProcessEvalFinished (CorEvalEventArgs evalArgs, bool isException) return; context.Session.OnEndEvaluating (); evalArgs.Continue = false; - tcs.TrySetResult(new OperationResult (evalArgs.Eval.Result, isException)); + if (aborted) { + tcs.TrySetCanceled (); + } + else { + tcs.TrySetResult(new OperationResult (evalArgs.Eval.Result, isException)); + } } void SubscribeOnEvals () @@ -69,6 +74,7 @@ public override string Description } readonly TaskCompletionSource> tcs = new TaskCompletionSource> (); + bool aborted = false; const int DelayAfterAbort = 500; protected override void AfterCancelledImpl (int elapsedAfterCancelMs) @@ -108,6 +114,7 @@ protected override Task> InvokeAsyncImpl (Cancellation protected override void CancelImpl ( ) { eval.Abort (); + aborted = true; } } } From e1f7852302e57f9b0954867ce1cdd806b2bf79d8 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Thu, 16 Mar 2017 21:57:29 +0300 Subject: [PATCH 14/21] More proper evaluation aborting --- Mono.Debugging.Win32/CorMethodCall.cs | 41 +++++++++++---------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/Mono.Debugging.Win32/CorMethodCall.cs b/Mono.Debugging.Win32/CorMethodCall.cs index 82a0d0f7b..ad017632d 100644 --- a/Mono.Debugging.Win32/CorMethodCall.cs +++ b/Mono.Debugging.Win32/CorMethodCall.cs @@ -1,7 +1,7 @@ -using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Samples.Debugging.CorDebug; using Microsoft.Samples.Debugging.CorDebug.NativeApi; +using Mono.Debugging.Client; using Mono.Debugging.Evaluation; namespace Mono.Debugging.Win32 @@ -40,10 +40,12 @@ void DoProcessEvalFinished (CorEvalEventArgs evalArgs, bool isException) return; context.Session.OnEndEvaluating (); evalArgs.Continue = false; - if (aborted) { + if (Token.IsCancellationRequested) { + DebuggerLoggingService.LogMessage ("EvalFinished() but evaluation was cancelled"); tcs.TrySetCanceled (); } else { + DebuggerLoggingService.LogMessage ("EvalFinished(). Setting the result"); tcs.TrySetResult(new OperationResult (evalArgs.Eval.Result, isException)); } } @@ -66,7 +68,7 @@ public override string Description { var met = function.GetMethodInfo (context.Session); if (met == null) - return ""; + return "[Unknown method]"; if (met.DeclaringType == null) return met.Name; return met.DeclaringType.FullName + "." + met.Name; @@ -74,22 +76,8 @@ public override string Description } readonly TaskCompletionSource> tcs = new TaskCompletionSource> (); - bool aborted = false; - const int DelayAfterAbort = 500; - protected override void AfterCancelledImpl (int elapsedAfterCancelMs) - { - if (tcs.TrySetCanceled ()) { - // really cancelled for the first time not before. so we should check that we awaited necessary amout of time after Abort() call - // else if we return too earle after Abort() the process may be PROCESS_NOT_SYNCHRONIZED - if (elapsedAfterCancelMs < DelayAfterAbort) { - Thread.Sleep (DelayAfterAbort - elapsedAfterCancelMs); - } - } - context.Session.OnEndEvaluating (); - } - - protected override Task> InvokeAsyncImpl (CancellationToken token) + protected override Task> InvokeAsyncImpl () { SubscribeOnEvals (); @@ -102,8 +90,7 @@ protected override Task> InvokeAsyncImpl (Cancellation context.Session.OnStartEvaluating (); context.Session.Process.Continue (false); Task = tcs.Task; - // Don't pass token here, because it causes immediately task cancellation which must be performed by debugger event or real timeout - // ReSharper disable once MethodSupportsCancellation + // Don't pass token here, because it causes immediately task cancellation which must be performed by debugger event or real timeout return Task.ContinueWith (task => { UnSubcribeOnEvals (); return task.Result; @@ -111,10 +98,16 @@ protected override Task> InvokeAsyncImpl (Cancellation } - protected override void CancelImpl ( ) + protected override void AbortImpl (int abortCallTimes) { - eval.Abort (); - aborted = true; + if (abortCallTimes < 10) { + DebuggerLoggingService.LogMessage ("Calling Abort()"); + eval.Abort (); + } + else { + DebuggerLoggingService.LogMessage ("Calling RudeAbort()"); + eval.RudeAbort(); + } } } } From ae0eb0f998c0f9b9707d5552bb463eb0fca62fbb Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Thu, 16 Mar 2017 23:28:33 +0300 Subject: [PATCH 15/21] Trying to continue all threads if eval Abort() and RudeAbort() failed for many times. Maybe this may help to avoid hanging evaluations. (Moved from MD repo) --- Mono.Debugging.Win32/CorMethodCall.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Mono.Debugging.Win32/CorMethodCall.cs b/Mono.Debugging.Win32/CorMethodCall.cs index ad017632d..1b0495934 100644 --- a/Mono.Debugging.Win32/CorMethodCall.cs +++ b/Mono.Debugging.Win32/CorMethodCall.cs @@ -101,11 +101,22 @@ protected override Task> InvokeAsyncImpl () protected override void AbortImpl (int abortCallTimes) { if (abortCallTimes < 10) { - DebuggerLoggingService.LogMessage ("Calling Abort()"); + DebuggerLoggingService.LogMessage ("Calling Abort() for {0} time", abortCallTimes); eval.Abort (); } else { - DebuggerLoggingService.LogMessage ("Calling RudeAbort()"); + if (abortCallTimes == 20) { + // if Abort() and RudeAbort() didn't bring any result let's try to resume all the threads to free possible deadlocks in target process + // maybe this can help to abort hanging evaluations + DebuggerLoggingService.LogMessage ("RudeAbort() didn't stop eval after {0} times", abortCallTimes - 1); + DebuggerLoggingService.LogMessage ("Calling Stop()"); + context.Session.Process.Stop (0); + DebuggerLoggingService.LogMessage ("Calling SetAllThreadsDebugState(THREAD_RUN)"); + context.Session.Process.SetAllThreadsDebugState (CorDebugThreadState.THREAD_RUN, null); + DebuggerLoggingService.LogMessage ("Calling Continue()"); + context.Session.Process.Continue (false); + } + DebuggerLoggingService.LogMessage ("Calling RudeAbort() for {0} time", abortCallTimes); eval.RudeAbort(); } } From 29b6a9047bfce6b7d3937015e10388029adc6a4c Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Thu, 16 Mar 2017 23:29:35 +0300 Subject: [PATCH 16/21] Handle exceptions in AbortImpl() (Moved from MD repo) --- Mono.Debugging.Win32/CorMethodCall.cs | 55 ++++++++++++++++++--------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/Mono.Debugging.Win32/CorMethodCall.cs b/Mono.Debugging.Win32/CorMethodCall.cs index 1b0495934..f6b010014 100644 --- a/Mono.Debugging.Win32/CorMethodCall.cs +++ b/Mono.Debugging.Win32/CorMethodCall.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; using Microsoft.Samples.Debugging.CorDebug; using Microsoft.Samples.Debugging.CorDebug.NativeApi; using Mono.Debugging.Client; @@ -100,24 +102,41 @@ protected override Task> InvokeAsyncImpl () protected override void AbortImpl (int abortCallTimes) { - if (abortCallTimes < 10) { - DebuggerLoggingService.LogMessage ("Calling Abort() for {0} time", abortCallTimes); - eval.Abort (); - } - else { - if (abortCallTimes == 20) { - // if Abort() and RudeAbort() didn't bring any result let's try to resume all the threads to free possible deadlocks in target process - // maybe this can help to abort hanging evaluations - DebuggerLoggingService.LogMessage ("RudeAbort() didn't stop eval after {0} times", abortCallTimes - 1); - DebuggerLoggingService.LogMessage ("Calling Stop()"); - context.Session.Process.Stop (0); - DebuggerLoggingService.LogMessage ("Calling SetAllThreadsDebugState(THREAD_RUN)"); - context.Session.Process.SetAllThreadsDebugState (CorDebugThreadState.THREAD_RUN, null); - DebuggerLoggingService.LogMessage ("Calling Continue()"); - context.Session.Process.Continue (false); + try { + if (abortCallTimes < 10) { + DebuggerLoggingService.LogMessage ("Calling Abort() for {0} time", abortCallTimes); + eval.Abort (); + } + else { + if (abortCallTimes == 20) { + // if Abort() and RudeAbort() didn't bring any result let's try to resume all the threads to free possible deadlocks in target process + // maybe this can help to abort hanging evaluations + DebuggerLoggingService.LogMessage ("RudeAbort() didn't stop eval after {0} times", abortCallTimes - 1); + DebuggerLoggingService.LogMessage ("Calling Stop()"); + context.Session.Process.Stop (0); + DebuggerLoggingService.LogMessage ("Calling SetAllThreadsDebugState(THREAD_RUN)"); + context.Session.Process.SetAllThreadsDebugState (CorDebugThreadState.THREAD_RUN, null); + DebuggerLoggingService.LogMessage ("Calling Continue()"); + context.Session.Process.Continue (false); + } + DebuggerLoggingService.LogMessage ("Calling RudeAbort() for {0} time", abortCallTimes); + eval.RudeAbort(); + } + + } catch (COMException e) { + var hResult = e.ToHResult (); + switch (hResult) { + case HResult.CORDBG_E_PROCESS_TERMINATED: + DebuggerLoggingService.LogMessage ("Process was terminated. Set cancelled for eval"); + tcs.TrySetCanceled (); + return; + case HResult.CORDBG_E_OBJECT_NEUTERED: + DebuggerLoggingService.LogMessage ("Eval object was neutered. Set cancelled for eval"); + tcs.TrySetCanceled (); + return; } - DebuggerLoggingService.LogMessage ("Calling RudeAbort() for {0} time", abortCallTimes); - eval.RudeAbort(); + tcs.SetException (e); + throw; } } } From c594ffc225eabac20b0a6330a65fdb73a1ac7631 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 17 Mar 2017 16:13:33 +0300 Subject: [PATCH 17/21] Restore RemoteFrameObject for AsyncEvaluationTracker --- .../AsyncEvaluationTracker.cs | 296 +++++++++--------- 1 file changed, 148 insertions(+), 148 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs index ce6010777..a68e74c9e 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs @@ -1,148 +1,148 @@ -// AsyncEvaluationTracker.cs -// -// Author: -// Lluis Sanchez Gual -// -// Copyright (c) 2008 Novell, Inc (http://www.novell.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// - -using System; -using System.Collections.Generic; -using Mono.Debugging.Client; -using Mono.Debugging.Backend; - -namespace Mono.Debugging.Evaluation -{ - public delegate ObjectValue ObjectEvaluatorDelegate (); - - /// - /// This class can be used to generate an ObjectValue using a provided evaluation delegate. - /// The value is initialy evaluated synchronously (blocking the caller). If no result - /// is obtained after a short period (provided in the WaitTime property), evaluation - /// will then be made asynchronous and the Run method will immediately return an ObjectValue - /// with the Evaluating state. - /// - public class AsyncEvaluationTracker: IObjectValueUpdater, IDisposable - { - Dictionary asyncCallbacks = new Dictionary (); - Dictionary asyncResults = new Dictionary (); - int asyncCounter = 0; - int cancelTimestamp = 0; - TimedEvaluator runner = new TimedEvaluator (); - - public int WaitTime { - get { return runner.RunTimeout; } - set { runner.RunTimeout = value; } - } - - public bool IsEvaluating { - get { return runner.IsEvaluating; } - } - - public ObjectValue Run (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator) - { - string id; - int tid; - lock (asyncCallbacks) { - tid = asyncCounter++; - id = tid.ToString (); - } - - ObjectValue val = null; - bool done = runner.Run (delegate { - if (tid >= cancelTimestamp) - val = evaluator (); - }, - delegate { - if (tid >= cancelTimestamp) - OnEvaluationDone (id, val); - }); - - if (done) { - // 'val' may be null if the timed evaluator is disposed while evaluating - return val ?? ObjectValue.CreateUnknown (name); - } - - return ObjectValue.CreateEvaluating (this, new ObjectPath (id, name), flags); - } - - public void Dispose () - { - runner.Dispose (); - } - - - public void Stop () - { - lock (asyncCallbacks) { - cancelTimestamp = asyncCounter; - runner.CancelAll (); - foreach (var cb in asyncCallbacks.Values) { - try { - cb.UpdateValue (ObjectValue.CreateFatalError ("", "Canceled", ObjectValueFlags.None)); - } catch { - } - } - asyncCallbacks.Clear (); - asyncResults.Clear (); - } - } - - public void WaitForStopped () - { - runner.WaitForStopped (); - } - - void OnEvaluationDone (string id, ObjectValue val) - { - if (val == null) - val = ObjectValue.CreateUnknown (null); - UpdateCallback cb = null; - lock (asyncCallbacks) { - if (asyncCallbacks.TryGetValue (id, out cb)) { - try { - cb.UpdateValue (val); - } catch {} - asyncCallbacks.Remove (id); - } - else - asyncResults [id] = val; - } - } - - void IObjectValueUpdater.RegisterUpdateCallbacks (UpdateCallback[] callbacks) - { - foreach (UpdateCallback c in callbacks) { - lock (asyncCallbacks) { - ObjectValue val; - string id = c.Path[0]; - if (asyncResults.TryGetValue (id, out val)) { - c.UpdateValue (val); - asyncResults.Remove (id); - } else { - asyncCallbacks [id] = c; - } - } - } - } - } -} +// AsyncEvaluationTracker.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public delegate ObjectValue ObjectEvaluatorDelegate (); + + /// + /// This class can be used to generate an ObjectValue using a provided evaluation delegate. + /// The value is initialy evaluated synchronously (blocking the caller). If no result + /// is obtained after a short period (provided in the WaitTime property), evaluation + /// will then be made asynchronous and the Run method will immediately return an ObjectValue + /// with the Evaluating state. + /// + public class AsyncEvaluationTracker: RemoteFrameObject, IObjectValueUpdater, IDisposable + { + Dictionary asyncCallbacks = new Dictionary (); + Dictionary asyncResults = new Dictionary (); + int asyncCounter = 0; + int cancelTimestamp = 0; + TimedEvaluator runner = new TimedEvaluator (); + + public int WaitTime { + get { return runner.RunTimeout; } + set { runner.RunTimeout = value; } + } + + public bool IsEvaluating { + get { return runner.IsEvaluating; } + } + + public ObjectValue Run (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator) + { + string id; + int tid; + lock (asyncCallbacks) { + tid = asyncCounter++; + id = tid.ToString (); + } + + ObjectValue val = null; + bool done = runner.Run (delegate { + if (tid >= cancelTimestamp) + val = evaluator (); + }, + delegate { + if (tid >= cancelTimestamp) + OnEvaluationDone (id, val); + }); + + if (done) { + // 'val' may be null if the timed evaluator is disposed while evaluating + return val ?? ObjectValue.CreateUnknown (name); + } + + return ObjectValue.CreateEvaluating (this, new ObjectPath (id, name), flags); + } + + public void Dispose () + { + runner.Dispose (); + } + + + public void Stop () + { + lock (asyncCallbacks) { + cancelTimestamp = asyncCounter; + runner.CancelAll (); + foreach (var cb in asyncCallbacks.Values) { + try { + cb.UpdateValue (ObjectValue.CreateFatalError ("", "Canceled", ObjectValueFlags.None)); + } catch { + } + } + asyncCallbacks.Clear (); + asyncResults.Clear (); + } + } + + public void WaitForStopped () + { + runner.WaitForStopped (); + } + + void OnEvaluationDone (string id, ObjectValue val) + { + if (val == null) + val = ObjectValue.CreateUnknown (null); + UpdateCallback cb = null; + lock (asyncCallbacks) { + if (asyncCallbacks.TryGetValue (id, out cb)) { + try { + cb.UpdateValue (val); + } catch {} + asyncCallbacks.Remove (id); + } + else + asyncResults [id] = val; + } + } + + void IObjectValueUpdater.RegisterUpdateCallbacks (UpdateCallback[] callbacks) + { + foreach (UpdateCallback c in callbacks) { + lock (asyncCallbacks) { + ObjectValue val; + string id = c.Path[0]; + if (asyncResults.TryGetValue (id, out val)) { + c.UpdateValue (val); + asyncResults.Remove (id); + } else { + asyncCallbacks [id] = c; + } + } + } + } + } +} From 45b3c9684a5c7f5cedaf336b6d726f7b48fbc053 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 17 Mar 2017 16:14:41 +0300 Subject: [PATCH 18/21] Remove unused file IAsyncOperation.cs --- .../IAsyncOperation.cs | 22 -- Mono.Debugging/Mono.Debugging.csproj | 285 +++++++++--------- 2 files changed, 142 insertions(+), 165 deletions(-) delete mode 100644 Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs b/Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs deleted file mode 100644 index 73842e20f..000000000 --- a/Mono.Debugging/Mono.Debugging.Evaluation/IAsyncOperation.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Mono.Debugging.Evaluation -{ - public interface IAsyncOperation - { - /// - /// Called to invoke the operation. The execution must be asynchronous (it must return immediatelly). - /// - void BeginInvoke (); - - /// - /// Called to abort the execution of the operation. It has to throw an exception - /// if the operation can't be aborted. This operation must not block. The engine - /// will wait for the operation to be aborted by calling WaitForCompleted. - /// - void Abort (); - - /// - /// Waits until the operation has been completed or aborted. - /// - bool WaitForCompleted (int timeout); - } -} \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.csproj b/Mono.Debugging/Mono.Debugging.csproj index abeb836b3..f284bc170 100644 --- a/Mono.Debugging/Mono.Debugging.csproj +++ b/Mono.Debugging/Mono.Debugging.csproj @@ -1,143 +1,142 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2} - Library - Mono.Debugging - True - mono.debugging.snk - Mono.Debugging - - - True - full - False - bin\Debug - DEBUG - prompt - 4 - False - - - - 1591;1573 - bin\Debug\Mono.Debugging.xml - - - pdbonly - true - bin\Release - prompt - 4 - False - - - - true - 1591;1573 - bin\Release\Mono.Debugging.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {3B2A5653-EC97-4001-BB9B-D90F1AF2C371} - ICSharpCode.NRefactory - - - {53DCA265-3C3C-42F9-B647-F72BA678122B} - ICSharpCode.NRefactory.CSharp - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2} + Library + Mono.Debugging + True + mono.debugging.snk + Mono.Debugging + + + True + full + False + bin\Debug + DEBUG + prompt + 4 + False + + + + 1591;1573 + bin\Debug\Mono.Debugging.xml + + + pdbonly + true + bin\Release + prompt + 4 + False + + + + true + 1591;1573 + bin\Release\Mono.Debugging.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371} + ICSharpCode.NRefactory + + + {53DCA265-3C3C-42F9-B647-F72BA678122B} + ICSharpCode.NRefactory.CSharp + + + \ No newline at end of file From 3f9b165b53f1fedc50da970c9b1d8c6ca08df744 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 17 Mar 2017 16:48:56 +0300 Subject: [PATCH 19/21] Break loop if disposed. --- .../AsyncOperationManager.cs | 380 +++++++++--------- 1 file changed, 192 insertions(+), 188 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index c603ff227..95b495b55 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -1,189 +1,193 @@ -// RuntimeInvokeManager.cs -// -// Author: -// Lluis Sanchez Gual -// -// Copyright (c) 2008 Novell, Inc (http://www.novell.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Mono.Debugging.Client; - -namespace Mono.Debugging.Evaluation -{ - public class AsyncOperationManager : IDisposable - { - readonly HashSet currentOperations = new HashSet (); - bool disposed = false; - const int ShortCancelTimeout = 100; - - static bool IsOperationCancelledException (Exception e, int depth = 4) - { - if (e is OperationCanceledException) - return true; - var aggregateException = e as AggregateException; - - if (depth > 0 && aggregateException != null) { - foreach (var innerException in aggregateException.InnerExceptions) { - if (IsOperationCancelledException (innerException, depth - 1)) - return true; - } - } - return false; - } - - public OperationResult Invoke (AsyncOperationBase mc, int timeout) - { - if (timeout <= 0) - throw new ArgumentOutOfRangeException("timeout", timeout, "timeout must be greater than 0"); - - Task> task; - var description = mc.Description; - lock (currentOperations) { - if (disposed) - throw new ObjectDisposedException ("Already disposed"); - DebuggerLoggingService.LogMessage (string.Format("Starting invoke for {0}", description)); - task = mc.InvokeAsync (); - currentOperations.Add (mc); - } - - bool cancelledAfterTimeout = false; - try { - if (task.Wait (timeout)) { - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} succeeded in {1} ms", description, timeout)); - return task.Result; - } - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} timed out after {1} ms. Cancelling.", description, timeout)); - mc.Abort (); - try { - WaitAfterCancel (mc); - } - catch (Exception e) { - if (IsOperationCancelledException (e)) { - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled after timeout", description)); - cancelledAfterTimeout = true; - } - throw; - } - DebuggerLoggingService.LogMessage (string.Format ("{0} cancelling timed out", description)); - throw new TimeOutException (); - } - catch (Exception e) { - if (IsOperationCancelledException (e)) { - if (cancelledAfterTimeout) - throw new TimeOutException (); - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled outside before timeout", description)); - throw new EvaluatorAbortedException (); - } - throw; - } - finally { - lock (currentOperations) { - currentOperations.Remove (mc); - } - } - } - - - public event EventHandler BusyStateChanged = delegate { }; - - void ChangeBusyState (bool busy, string description) - { - try { - BusyStateChanged (this, new BusyStateEventArgs {IsBusy = busy, Description = description}); - } - catch (Exception e) { - DebuggerLoggingService.LogError ("Exception during ChangeBusyState", e); - } - } - - void WaitAfterCancel (IAsyncOperationBase op) - { - var desc = op.Description; - DebuggerLoggingService.LogMessage (string.Format ("Waiting for cancel of invoke {0}", desc)); - if (!op.RawTask.Wait (ShortCancelTimeout)) { - try { - ChangeBusyState (true, desc); - while (true) { - op.Abort (); - if (op.RawTask.Wait (ShortCancelTimeout)) - break; - } - } - finally { - ChangeBusyState (false, desc); - } - } - } - - public void AbortAll () - { - DebuggerLoggingService.LogMessage ("Aborting all the current invocations"); - List copy; - lock (currentOperations) { - if (disposed) throw new ObjectDisposedException ("Already disposed"); - copy = currentOperations.ToList (); - currentOperations.Clear (); - } - - CancelOperations (copy, true); - } - - void CancelOperations (List operations, bool wait) - { - foreach (var operation in operations) { - var taskDescription = operation.Description; - try { - operation.Abort (); - if (wait) { - WaitAfterCancel (operation); - } - } - catch (Exception e) { - if (IsOperationCancelledException (e)) { - DebuggerLoggingService.LogMessage (string.Format ("Invocation of {0} cancelled in CancelOperations()", taskDescription)); - } - else { - DebuggerLoggingService.LogError (string.Format ("Invocation of {0} thrown an exception in CancelOperations()", taskDescription), e); - } - } - } - } - - - public void Dispose () - { - List copy; - lock (currentOperations) { - if (disposed) throw new ObjectDisposedException ("Already disposed"); - disposed = true; - copy = currentOperations.ToList (); - currentOperations.Clear (); - } - // don't wait on dispose - CancelOperations (copy, wait: false); - } - } +// RuntimeInvokeManager.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class AsyncOperationManager : IDisposable + { + readonly HashSet currentOperations = new HashSet (); + bool disposed = false; + const int ShortCancelTimeout = 100; + + static bool IsOperationCancelledException (Exception e, int depth = 4) + { + if (e is OperationCanceledException) + return true; + var aggregateException = e as AggregateException; + + if (depth > 0 && aggregateException != null) { + foreach (var innerException in aggregateException.InnerExceptions) { + if (IsOperationCancelledException (innerException, depth - 1)) + return true; + } + } + return false; + } + + public OperationResult Invoke (AsyncOperationBase mc, int timeout) + { + if (timeout <= 0) + throw new ArgumentOutOfRangeException("timeout", timeout, "timeout must be greater than 0"); + + Task> task; + var description = mc.Description; + lock (currentOperations) { + if (disposed) + throw new ObjectDisposedException ("Already disposed"); + DebuggerLoggingService.LogMessage (string.Format("Starting invoke for {0}", description)); + task = mc.InvokeAsync (); + currentOperations.Add (mc); + } + + bool cancelledAfterTimeout = false; + try { + if (task.Wait (timeout)) { + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} succeeded in {1} ms", description, timeout)); + return task.Result; + } + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} timed out after {1} ms. Cancelling.", description, timeout)); + mc.Abort (); + try { + WaitAfterCancel (mc); + } + catch (Exception e) { + if (IsOperationCancelledException (e)) { + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled after timeout", description)); + cancelledAfterTimeout = true; + } + throw; + } + DebuggerLoggingService.LogMessage (string.Format ("{0} cancelling timed out", description)); + throw new TimeOutException (); + } + catch (Exception e) { + if (IsOperationCancelledException (e)) { + if (cancelledAfterTimeout) + throw new TimeOutException (); + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled outside before timeout", description)); + throw new EvaluatorAbortedException (); + } + throw; + } + finally { + lock (currentOperations) { + currentOperations.Remove (mc); + } + } + } + + + public event EventHandler BusyStateChanged = delegate { }; + + void ChangeBusyState (bool busy, string description) + { + try { + BusyStateChanged (this, new BusyStateEventArgs {IsBusy = busy, Description = description}); + } + catch (Exception e) { + DebuggerLoggingService.LogError ("Exception during ChangeBusyState", e); + } + } + + void WaitAfterCancel (IAsyncOperationBase op) + { + var desc = op.Description; + DebuggerLoggingService.LogMessage (string.Format ("Waiting for cancel of invoke {0}", desc)); + if (!op.RawTask.Wait (ShortCancelTimeout)) { + try { + ChangeBusyState (true, desc); + while (true) { + lock (currentOperations) { + if (disposed) + break; + } + op.Abort (); + if (op.RawTask.Wait (ShortCancelTimeout)) + break; + } + } + finally { + ChangeBusyState (false, desc); + } + } + } + + public void AbortAll () + { + DebuggerLoggingService.LogMessage ("Aborting all the current invocations"); + List copy; + lock (currentOperations) { + if (disposed) throw new ObjectDisposedException ("Already disposed"); + copy = currentOperations.ToList (); + currentOperations.Clear (); + } + + CancelOperations (copy, true); + } + + void CancelOperations (List operations, bool wait) + { + foreach (var operation in operations) { + var taskDescription = operation.Description; + try { + operation.Abort (); + if (wait) { + WaitAfterCancel (operation); + } + } + catch (Exception e) { + if (IsOperationCancelledException (e)) { + DebuggerLoggingService.LogMessage (string.Format ("Invocation of {0} cancelled in CancelOperations()", taskDescription)); + } + else { + DebuggerLoggingService.LogError (string.Format ("Invocation of {0} thrown an exception in CancelOperations()", taskDescription), e); + } + } + } + } + + + public void Dispose () + { + List copy; + lock (currentOperations) { + if (disposed) throw new ObjectDisposedException ("Already disposed"); + disposed = true; + copy = currentOperations.ToList (); + currentOperations.Clear (); + } + // don't wait on dispose + CancelOperations (copy, wait: false); + } + } } \ No newline at end of file From c6f114860bd83f6a3bf14c79ec6b310ab2c52595 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 17 Mar 2017 17:20:44 +0300 Subject: [PATCH 20/21] Line endings fix --- .../AsyncEvaluationTracker.cs | 296 +++++++------- .../AsyncOperationManager.cs | 384 +++++++++--------- Mono.Debugging/Mono.Debugging.csproj | 282 ++++++------- 3 files changed, 481 insertions(+), 481 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs index a68e74c9e..020603133 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncEvaluationTracker.cs @@ -1,148 +1,148 @@ -// AsyncEvaluationTracker.cs -// -// Author: -// Lluis Sanchez Gual -// -// Copyright (c) 2008 Novell, Inc (http://www.novell.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// - -using System; -using System.Collections.Generic; -using Mono.Debugging.Client; -using Mono.Debugging.Backend; - -namespace Mono.Debugging.Evaluation -{ - public delegate ObjectValue ObjectEvaluatorDelegate (); - - /// - /// This class can be used to generate an ObjectValue using a provided evaluation delegate. - /// The value is initialy evaluated synchronously (blocking the caller). If no result - /// is obtained after a short period (provided in the WaitTime property), evaluation - /// will then be made asynchronous and the Run method will immediately return an ObjectValue - /// with the Evaluating state. - /// - public class AsyncEvaluationTracker: RemoteFrameObject, IObjectValueUpdater, IDisposable - { - Dictionary asyncCallbacks = new Dictionary (); - Dictionary asyncResults = new Dictionary (); - int asyncCounter = 0; - int cancelTimestamp = 0; - TimedEvaluator runner = new TimedEvaluator (); - - public int WaitTime { - get { return runner.RunTimeout; } - set { runner.RunTimeout = value; } - } - - public bool IsEvaluating { - get { return runner.IsEvaluating; } - } - - public ObjectValue Run (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator) - { - string id; - int tid; - lock (asyncCallbacks) { - tid = asyncCounter++; - id = tid.ToString (); - } - - ObjectValue val = null; - bool done = runner.Run (delegate { - if (tid >= cancelTimestamp) - val = evaluator (); - }, - delegate { - if (tid >= cancelTimestamp) - OnEvaluationDone (id, val); - }); - - if (done) { - // 'val' may be null if the timed evaluator is disposed while evaluating - return val ?? ObjectValue.CreateUnknown (name); - } - - return ObjectValue.CreateEvaluating (this, new ObjectPath (id, name), flags); - } - - public void Dispose () - { - runner.Dispose (); - } - - - public void Stop () - { - lock (asyncCallbacks) { - cancelTimestamp = asyncCounter; - runner.CancelAll (); - foreach (var cb in asyncCallbacks.Values) { - try { - cb.UpdateValue (ObjectValue.CreateFatalError ("", "Canceled", ObjectValueFlags.None)); - } catch { - } - } - asyncCallbacks.Clear (); - asyncResults.Clear (); - } - } - - public void WaitForStopped () - { - runner.WaitForStopped (); - } - - void OnEvaluationDone (string id, ObjectValue val) - { - if (val == null) - val = ObjectValue.CreateUnknown (null); - UpdateCallback cb = null; - lock (asyncCallbacks) { - if (asyncCallbacks.TryGetValue (id, out cb)) { - try { - cb.UpdateValue (val); - } catch {} - asyncCallbacks.Remove (id); - } - else - asyncResults [id] = val; - } - } - - void IObjectValueUpdater.RegisterUpdateCallbacks (UpdateCallback[] callbacks) - { - foreach (UpdateCallback c in callbacks) { - lock (asyncCallbacks) { - ObjectValue val; - string id = c.Path[0]; - if (asyncResults.TryGetValue (id, out val)) { - c.UpdateValue (val); - asyncResults.Remove (id); - } else { - asyncCallbacks [id] = c; - } - } - } - } - } -} +// AsyncEvaluationTracker.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public delegate ObjectValue ObjectEvaluatorDelegate (); + + /// + /// This class can be used to generate an ObjectValue using a provided evaluation delegate. + /// The value is initialy evaluated synchronously (blocking the caller). If no result + /// is obtained after a short period (provided in the WaitTime property), evaluation + /// will then be made asynchronous and the Run method will immediately return an ObjectValue + /// with the Evaluating state. + /// + public class AsyncEvaluationTracker: RemoteFrameObject, IObjectValueUpdater, IDisposable + { + Dictionary asyncCallbacks = new Dictionary (); + Dictionary asyncResults = new Dictionary (); + int asyncCounter = 0; + int cancelTimestamp = 0; + TimedEvaluator runner = new TimedEvaluator (); + + public int WaitTime { + get { return runner.RunTimeout; } + set { runner.RunTimeout = value; } + } + + public bool IsEvaluating { + get { return runner.IsEvaluating; } + } + + public ObjectValue Run (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator) + { + string id; + int tid; + lock (asyncCallbacks) { + tid = asyncCounter++; + id = tid.ToString (); + } + + ObjectValue val = null; + bool done = runner.Run (delegate { + if (tid >= cancelTimestamp) + val = evaluator (); + }, + delegate { + if (tid >= cancelTimestamp) + OnEvaluationDone (id, val); + }); + + if (done) { + // 'val' may be null if the timed evaluator is disposed while evaluating + return val ?? ObjectValue.CreateUnknown (name); + } + + return ObjectValue.CreateEvaluating (this, new ObjectPath (id, name), flags); + } + + public void Dispose () + { + runner.Dispose (); + } + + + public void Stop () + { + lock (asyncCallbacks) { + cancelTimestamp = asyncCounter; + runner.CancelAll (); + foreach (var cb in asyncCallbacks.Values) { + try { + cb.UpdateValue (ObjectValue.CreateFatalError ("", "Canceled", ObjectValueFlags.None)); + } catch { + } + } + asyncCallbacks.Clear (); + asyncResults.Clear (); + } + } + + public void WaitForStopped () + { + runner.WaitForStopped (); + } + + void OnEvaluationDone (string id, ObjectValue val) + { + if (val == null) + val = ObjectValue.CreateUnknown (null); + UpdateCallback cb = null; + lock (asyncCallbacks) { + if (asyncCallbacks.TryGetValue (id, out cb)) { + try { + cb.UpdateValue (val); + } catch {} + asyncCallbacks.Remove (id); + } + else + asyncResults [id] = val; + } + } + + void IObjectValueUpdater.RegisterUpdateCallbacks (UpdateCallback[] callbacks) + { + foreach (UpdateCallback c in callbacks) { + lock (asyncCallbacks) { + ObjectValue val; + string id = c.Path[0]; + if (asyncResults.TryGetValue (id, out val)) { + c.UpdateValue (val); + asyncResults.Remove (id); + } else { + asyncCallbacks [id] = c; + } + } + } + } + } +} diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index 95b495b55..275b69966 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -1,193 +1,193 @@ -// RuntimeInvokeManager.cs -// -// Author: -// Lluis Sanchez Gual -// -// Copyright (c) 2008 Novell, Inc (http://www.novell.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Mono.Debugging.Client; - -namespace Mono.Debugging.Evaluation -{ - public class AsyncOperationManager : IDisposable - { - readonly HashSet currentOperations = new HashSet (); - bool disposed = false; - const int ShortCancelTimeout = 100; - - static bool IsOperationCancelledException (Exception e, int depth = 4) - { - if (e is OperationCanceledException) - return true; - var aggregateException = e as AggregateException; - - if (depth > 0 && aggregateException != null) { - foreach (var innerException in aggregateException.InnerExceptions) { - if (IsOperationCancelledException (innerException, depth - 1)) - return true; - } - } - return false; - } - - public OperationResult Invoke (AsyncOperationBase mc, int timeout) - { - if (timeout <= 0) - throw new ArgumentOutOfRangeException("timeout", timeout, "timeout must be greater than 0"); - - Task> task; - var description = mc.Description; - lock (currentOperations) { - if (disposed) - throw new ObjectDisposedException ("Already disposed"); - DebuggerLoggingService.LogMessage (string.Format("Starting invoke for {0}", description)); - task = mc.InvokeAsync (); - currentOperations.Add (mc); - } - - bool cancelledAfterTimeout = false; - try { - if (task.Wait (timeout)) { - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} succeeded in {1} ms", description, timeout)); - return task.Result; - } - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} timed out after {1} ms. Cancelling.", description, timeout)); - mc.Abort (); - try { - WaitAfterCancel (mc); - } - catch (Exception e) { - if (IsOperationCancelledException (e)) { - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled after timeout", description)); - cancelledAfterTimeout = true; - } - throw; - } - DebuggerLoggingService.LogMessage (string.Format ("{0} cancelling timed out", description)); - throw new TimeOutException (); - } - catch (Exception e) { - if (IsOperationCancelledException (e)) { - if (cancelledAfterTimeout) - throw new TimeOutException (); - DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled outside before timeout", description)); - throw new EvaluatorAbortedException (); - } - throw; - } - finally { - lock (currentOperations) { - currentOperations.Remove (mc); - } - } - } - - - public event EventHandler BusyStateChanged = delegate { }; - - void ChangeBusyState (bool busy, string description) - { - try { - BusyStateChanged (this, new BusyStateEventArgs {IsBusy = busy, Description = description}); - } - catch (Exception e) { - DebuggerLoggingService.LogError ("Exception during ChangeBusyState", e); - } - } - - void WaitAfterCancel (IAsyncOperationBase op) - { - var desc = op.Description; - DebuggerLoggingService.LogMessage (string.Format ("Waiting for cancel of invoke {0}", desc)); - if (!op.RawTask.Wait (ShortCancelTimeout)) { - try { - ChangeBusyState (true, desc); - while (true) { - lock (currentOperations) { - if (disposed) - break; - } - op.Abort (); - if (op.RawTask.Wait (ShortCancelTimeout)) - break; - } - } - finally { - ChangeBusyState (false, desc); - } - } - } - - public void AbortAll () - { - DebuggerLoggingService.LogMessage ("Aborting all the current invocations"); - List copy; - lock (currentOperations) { - if (disposed) throw new ObjectDisposedException ("Already disposed"); - copy = currentOperations.ToList (); - currentOperations.Clear (); - } - - CancelOperations (copy, true); - } - - void CancelOperations (List operations, bool wait) - { - foreach (var operation in operations) { - var taskDescription = operation.Description; - try { - operation.Abort (); - if (wait) { - WaitAfterCancel (operation); - } - } - catch (Exception e) { - if (IsOperationCancelledException (e)) { - DebuggerLoggingService.LogMessage (string.Format ("Invocation of {0} cancelled in CancelOperations()", taskDescription)); - } - else { - DebuggerLoggingService.LogError (string.Format ("Invocation of {0} thrown an exception in CancelOperations()", taskDescription), e); - } - } - } - } - - - public void Dispose () - { - List copy; - lock (currentOperations) { - if (disposed) throw new ObjectDisposedException ("Already disposed"); - disposed = true; - copy = currentOperations.ToList (); - currentOperations.Clear (); - } - // don't wait on dispose - CancelOperations (copy, wait: false); - } - } +// RuntimeInvokeManager.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Mono.Debugging.Client; + +namespace Mono.Debugging.Evaluation +{ + public class AsyncOperationManager : IDisposable + { + readonly HashSet currentOperations = new HashSet (); + bool disposed = false; + const int ShortCancelTimeout = 100; + + static bool IsOperationCancelledException (Exception e, int depth = 4) + { + if (e is OperationCanceledException) + return true; + var aggregateException = e as AggregateException; + + if (depth > 0 && aggregateException != null) { + foreach (var innerException in aggregateException.InnerExceptions) { + if (IsOperationCancelledException (innerException, depth - 1)) + return true; + } + } + return false; + } + + public OperationResult Invoke (AsyncOperationBase mc, int timeout) + { + if (timeout <= 0) + throw new ArgumentOutOfRangeException("timeout", timeout, "timeout must be greater than 0"); + + Task> task; + var description = mc.Description; + lock (currentOperations) { + if (disposed) + throw new ObjectDisposedException ("Already disposed"); + DebuggerLoggingService.LogMessage (string.Format("Starting invoke for {0}", description)); + task = mc.InvokeAsync (); + currentOperations.Add (mc); + } + + bool cancelledAfterTimeout = false; + try { + if (task.Wait (timeout)) { + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} succeeded in {1} ms", description, timeout)); + return task.Result; + } + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} timed out after {1} ms. Cancelling.", description, timeout)); + mc.Abort (); + try { + WaitAfterCancel (mc); + } + catch (Exception e) { + if (IsOperationCancelledException (e)) { + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled after timeout", description)); + cancelledAfterTimeout = true; + } + throw; + } + DebuggerLoggingService.LogMessage (string.Format ("{0} cancelling timed out", description)); + throw new TimeOutException (); + } + catch (Exception e) { + if (IsOperationCancelledException (e)) { + if (cancelledAfterTimeout) + throw new TimeOutException (); + DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} was cancelled outside before timeout", description)); + throw new EvaluatorAbortedException (); + } + throw; + } + finally { + lock (currentOperations) { + currentOperations.Remove (mc); + } + } + } + + + public event EventHandler BusyStateChanged = delegate { }; + + void ChangeBusyState (bool busy, string description) + { + try { + BusyStateChanged (this, new BusyStateEventArgs {IsBusy = busy, Description = description}); + } + catch (Exception e) { + DebuggerLoggingService.LogError ("Exception during ChangeBusyState", e); + } + } + + void WaitAfterCancel (IAsyncOperationBase op) + { + var desc = op.Description; + DebuggerLoggingService.LogMessage (string.Format ("Waiting for cancel of invoke {0}", desc)); + if (!op.RawTask.Wait (ShortCancelTimeout)) { + try { + ChangeBusyState (true, desc); + while (true) { + lock (currentOperations) { + if (disposed) + break; + } + op.Abort (); + if (op.RawTask.Wait (ShortCancelTimeout)) + break; + } + } + finally { + ChangeBusyState (false, desc); + } + } + } + + public void AbortAll () + { + DebuggerLoggingService.LogMessage ("Aborting all the current invocations"); + List copy; + lock (currentOperations) { + if (disposed) throw new ObjectDisposedException ("Already disposed"); + copy = currentOperations.ToList (); + currentOperations.Clear (); + } + + CancelOperations (copy, true); + } + + void CancelOperations (List operations, bool wait) + { + foreach (var operation in operations) { + var taskDescription = operation.Description; + try { + operation.Abort (); + if (wait) { + WaitAfterCancel (operation); + } + } + catch (Exception e) { + if (IsOperationCancelledException (e)) { + DebuggerLoggingService.LogMessage (string.Format ("Invocation of {0} cancelled in CancelOperations()", taskDescription)); + } + else { + DebuggerLoggingService.LogError (string.Format ("Invocation of {0} thrown an exception in CancelOperations()", taskDescription), e); + } + } + } + } + + + public void Dispose () + { + List copy; + lock (currentOperations) { + if (disposed) throw new ObjectDisposedException ("Already disposed"); + disposed = true; + copy = currentOperations.ToList (); + currentOperations.Clear (); + } + // don't wait on dispose + CancelOperations (copy, wait: false); + } + } } \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.csproj b/Mono.Debugging/Mono.Debugging.csproj index f284bc170..db6904f53 100644 --- a/Mono.Debugging/Mono.Debugging.csproj +++ b/Mono.Debugging/Mono.Debugging.csproj @@ -1,142 +1,142 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2} - Library - Mono.Debugging - True - mono.debugging.snk - Mono.Debugging - - - True - full - False - bin\Debug - DEBUG - prompt - 4 - False - - - - 1591;1573 - bin\Debug\Mono.Debugging.xml - - - pdbonly - true - bin\Release - prompt - 4 - False - - - - true - 1591;1573 - bin\Release\Mono.Debugging.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {3B2A5653-EC97-4001-BB9B-D90F1AF2C371} - ICSharpCode.NRefactory - - - {53DCA265-3C3C-42F9-B647-F72BA678122B} - ICSharpCode.NRefactory.CSharp - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2} + Library + Mono.Debugging + True + mono.debugging.snk + Mono.Debugging + + + True + full + False + bin\Debug + DEBUG + prompt + 4 + False + + + + 1591;1573 + bin\Debug\Mono.Debugging.xml + + + pdbonly + true + bin\Release + prompt + 4 + False + + + + true + 1591;1573 + bin\Release\Mono.Debugging.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371} + ICSharpCode.NRefactory + + + {53DCA265-3C3C-42F9-B647-F72BA678122B} + ICSharpCode.NRefactory.CSharp + + \ No newline at end of file From e2cf427a3a89fcda8b41b38ad5c64b437f93589b Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 17 Mar 2017 18:08:36 +0300 Subject: [PATCH 21/21] Don't call Abort() if operation is alredy in aborting state --- .../AsyncOperationBase.cs | 3 +++ .../AsyncOperationManager.cs | 21 +++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs index 25b09c880..7330555a9 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs @@ -32,6 +32,7 @@ public interface IAsyncOperationBase { Task RawTask { get; } string Description { get; } + bool AbortCalled { get; } void Abort (); } @@ -59,6 +60,8 @@ public Task RawTask /// protected CancellationToken Token { get { return tokenSource.Token; } } + public bool AbortCalled { get { return Token.IsCancellationRequested; } } + public void Abort () { try { diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs index 275b69966..162b9d47e 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -76,9 +76,12 @@ public OperationResult Invoke (AsyncOperationBase mc, in return task.Result; } DebuggerLoggingService.LogMessage (string.Format ("Invoke {0} timed out after {1} ms. Cancelling.", description, timeout)); - mc.Abort (); + var abortCalled = mc.AbortCalled; + // if abort was already called (in AbortAll/Dispose) don't call it again, just wait + if (!abortCalled) + mc.Abort (); try { - WaitAfterCancel (mc); + WaitAfterCancel (mc, abortCalled); } catch (Exception e) { if (IsOperationCancelledException (e)) { @@ -119,7 +122,7 @@ void ChangeBusyState (bool busy, string description) } } - void WaitAfterCancel (IAsyncOperationBase op) + void WaitAfterCancel (IAsyncOperationBase op, bool onlyWait) { var desc = op.Description; DebuggerLoggingService.LogMessage (string.Format ("Waiting for cancel of invoke {0}", desc)); @@ -131,7 +134,9 @@ void WaitAfterCancel (IAsyncOperationBase op) if (disposed) break; } - op.Abort (); + if (!onlyWait) { + op.Abort (); + } if (op.RawTask.Wait (ShortCancelTimeout)) break; } @@ -160,9 +165,13 @@ void CancelOperations (List operations, bool wait) foreach (var operation in operations) { var taskDescription = operation.Description; try { - operation.Abort (); + var abortCalled = operation.AbortCalled; + // if abort was already called (in AbortAll/Dispose) don't call it again, just wait + if (!abortCalled) { + operation.Abort (); + } if (wait) { - WaitAfterCancel (operation); + WaitAfterCancel (operation, abortCalled); } } catch (Exception e) {