From 90a1f51b6501ea178df4a40095833c8acd8a9442 Mon Sep 17 00:00:00 2001 From: "Artem.Bukhonov" Date: Thu, 13 Aug 2015 22:09:18 +0300 Subject: [PATCH 01/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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) { From 4c1b70dbd801a227bd08c02f736de267474daea4 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Thu, 9 Feb 2017 00:36:56 +0300 Subject: [PATCH 22/26] IObjectValueSource.SetValue now throws ValueModificationException if modification is failed instead of supressing the exception. This allows to show the error info to user at the call site (e.g. in message box). ValueReference.SetValue() and ArrayElementGroup.SetValue() implementations unified and moved to ValueModificationUtil. (cherry picked from commit 316b7cc) --- .../IObjectValueSource.cs | 7 +++ .../ArrayElementGroup.cs | 33 +++--------- .../ValueModificationException.cs | 19 +++++++ .../ValueModificationUtil.cs | 50 +++++++++++++++++++ .../ValueReference.cs | 26 ++-------- Mono.Debugging/Mono.Debugging.csproj | 4 +- 6 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationException.cs create mode 100644 Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs diff --git a/Mono.Debugging/Mono.Debugging.Backend/IObjectValueSource.cs b/Mono.Debugging/Mono.Debugging.Backend/IObjectValueSource.cs index c9018e5f2..b2c79e67e 100644 --- a/Mono.Debugging/Mono.Debugging.Backend/IObjectValueSource.cs +++ b/Mono.Debugging/Mono.Debugging.Backend/IObjectValueSource.cs @@ -28,12 +28,19 @@ using System; using Mono.Debugging.Client; +using Mono.Debugging.Evaluation; namespace Mono.Debugging.Backend { public interface IObjectValueSource: IDebuggerBackendObject { ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options); + + /// + /// Updates the value with the result of evaluation of expression + /// + /// call site should catch this exception and show it to user in pretty way (e.g. in message box) + /// All other exceptions indicate error and should be logged EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options); ObjectValue GetValue (ObjectPath path, EvaluationOptions options); diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs index b49e5879a..126ffee5b 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs @@ -277,37 +277,18 @@ public static string GetArrayDescription (int[] bounds) return sb.ToString (); } - - public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) + + EvaluationResult IObjectValueSource.SetValue (ObjectPath path, string value, EvaluationOptions options) { if (path.Length != 2) - throw new NotSupportedException (); + throw new InvalidOperationException (string.Format ("ObjectPath for array element is invalid: {0}", path)); int[] idx = StringToIndices (path [1]); - - object val; - try { - EvaluationContext cctx = ctx.Clone (); - EvaluationOptions ops = options ?? cctx.Options; - ops.AllowMethodEvaluation = true; - ops.AllowTargetInvoke = true; - cctx.Options = ops; - ValueReference var = ctx.Evaluator.Evaluate (ctx, value, array.ElementType); - val = var.Value; - val = ctx.Adapter.Convert (ctx, val, array.ElementType); - array.SetElement (idx, val); - } catch { - val = array.GetElement (idx); - } - try { - return ctx.Evaluator.TargetObjectToExpression (ctx, val); - } catch (Exception ex) { - ctx.WriteDebuggerError (ex); - return new EvaluationResult ("? (" + ex.Message + ")"); - } + var cctx = ctx.WithOptions (options); + return ValueModificationUtil.ModifyValue (cctx, value, array.ElementType, newVal => array.SetElement (idx, newVal)); } - - public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) + + ObjectValue IObjectValueSource.GetValue (ObjectPath path, EvaluationOptions options) { if (path.Length != 2) throw new NotSupportedException (); diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationException.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationException.cs new file mode 100644 index 000000000..356ced0e2 --- /dev/null +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationException.cs @@ -0,0 +1,19 @@ +using System; + +namespace Mono.Debugging.Evaluation +{ + public class ValueModificationException : Exception + { + public ValueModificationException () + { + } + + public ValueModificationException (string message) : base (message) + { + } + + public ValueModificationException (string message, Exception innerException) : base (message, innerException) + { + } + } +} \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs new file mode 100644 index 000000000..a9e2024a9 --- /dev/null +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs @@ -0,0 +1,50 @@ +using System; +using Mono.Debugging.Backend; + +namespace Mono.Debugging.Evaluation +{ + public static class ValueModificationUtil + { + internal static ValueReference EvaluateRightHandValue (EvaluationContext context, string value, object expectedType) + { + context.Options.AllowMethodEvaluation = true; + context.Options.AllowTargetInvoke = true; + try { + return context.Evaluator.Evaluate (context, value, expectedType); + } catch (Exception e) { + throw new ValueModificationException(string.Format ("Cannot evaluate '{0}': {1}", value, e.Message), e); + } + } + + internal static object ConvertRightHandValue (EvaluationContext context, object value, object expectedType) + { + try { + return context.Adapter.Convert (context, value, expectedType); + } catch (Exception e) { + throw new ValueModificationException(string.Format ("Conversion error: {0}", e.Message), e); + } + } + + internal static EvaluationResult ModifyValue (EvaluationContext context, string value, object expectedType, Action valueSetter) + { + var rightHandValue = EvaluateRightHandValue (context, value, expectedType); + + object val; + try { + val = rightHandValue.Value; + } catch (Exception e) { + throw new ValueModificationException(string.Format ("Cannot get real object of {0}", value), e); + } + var convertedValue = ConvertRightHandValue (context, val, expectedType); + try { + valueSetter (convertedValue); + } catch (Exception e) { + throw new ValueModificationException(string.Format ("Error while assigning new value to object: {0}", e.Message), e); + } + // don't wrap with try-catch it, this call normally should not throw exceptions produced by wrong user input. + // If exception was occured this means something has gone wrong in we have to report it in log + return context.Evaluator.TargetObjectToExpression (context, convertedValue); + } + + } +} \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs index 4bcb58b69..aea1a3aa9 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs @@ -148,29 +148,9 @@ ObjectValue IObjectValueSource.GetValue (ObjectPath path, EvaluationOptions opti EvaluationResult IObjectValueSource.SetValue (ObjectPath path, string value, EvaluationOptions options) { - try { - Context.WaitRuntimeInvokes (); - - var ctx = GetContext (options); - ctx.Options.AllowMethodEvaluation = true; - ctx.Options.AllowTargetInvoke = true; - - var vref = ctx.Evaluator.Evaluate (ctx, value, Type); - var newValue = ctx.Adapter.Convert (ctx, vref.Value, Type); - SetValue (ctx, newValue); - } catch (Exception ex) { - Context.WriteDebuggerError (ex); - Context.WriteDebuggerOutput ("Value assignment failed: {0}: {1}\n", ex.GetType (), ex.Message); - } - - try { - return Context.Evaluator.TargetObjectToExpression (Context, Value); - } catch (Exception ex) { - Context.WriteDebuggerError (ex); - Context.WriteDebuggerOutput ("Value assignment failed: {0}: {1}\n", ex.GetType (), ex.Message); - } - - return null; + Context.WaitRuntimeInvokes (); + var ctx = GetContext (options); + return ValueModificationUtil.ModifyValue (ctx, value, Type, newVal => SetValue (ctx, newVal)); } object IObjectValueSource.GetRawValue (ObjectPath path, EvaluationOptions options) diff --git a/Mono.Debugging/Mono.Debugging.csproj b/Mono.Debugging/Mono.Debugging.csproj index db6904f53..9daa62e83 100644 --- a/Mono.Debugging/Mono.Debugging.csproj +++ b/Mono.Debugging/Mono.Debugging.csproj @@ -1,4 +1,4 @@ - + Debug @@ -97,6 +97,7 @@ + @@ -125,6 +126,7 @@ + From 4e0e54ec205e694a2cc90570227137b17ddc1ddd Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Thu, 9 Feb 2017 00:37:48 +0300 Subject: [PATCH 23/26] ObjectValue.SetValue and Value property setter unified into one method. Documentation updated. (cherry picked from commit fdffbaa) --- .../Mono.Debugging.Client/ObjectValue.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs b/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs index 366150b21..a0bea3990 100644 --- a/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs +++ b/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs @@ -229,6 +229,9 @@ public string Name { /// /// Is thrown when trying to set a value on a read-only ObjectValue /// + /// + /// When value settings was failed. This exception should be shown to user. + /// /// /// This value is a string representation of the ObjectValue. The content depends on several evaluation /// options. For example, if ToString calls are enabled, this value will be the result of calling @@ -238,20 +241,13 @@ public string Name { /// will include the quotation marks and chars like '\' will be properly escaped. /// If you need to get the real CLR value of the object, use GetRawValue. /// + /// public virtual string Value { get { return value; } set { - if (IsReadOnly || source == null) - throw new InvalidOperationException ("Value is not editable"); - - EvaluationResult res = source.SetValue (path, value, null); - if (res != null) { - this.value = res.Value; - displayValue = res.DisplayValue; - isNull = value == null; - } + SetValue (value); } } @@ -291,6 +287,10 @@ public void SetValue (string value) /// /// Is thrown if the value is read-only /// + /// + /// When value settings was failed. This exception should be shown to user. + /// + /// public void SetValue (string value, EvaluationOptions options) { if (IsReadOnly || source == null) @@ -299,6 +299,7 @@ public void SetValue (string value, EvaluationOptions options) if (res != null) { this.value = res.Value; displayValue = res.DisplayValue; + isNull = value == null; } } From 487338dd4b150aed06122b87708bd98b4d80c2c0 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 10 Feb 2017 22:53:22 +0300 Subject: [PATCH 24/26] Handle exceptions in ArrayValueReference.Value getter to be properly shown in watch view (cherry picked from commit abcd933) --- .../Mono.Debugging.Evaluation/ArrayValueReference.cs | 6 +++++- .../Mono.Debugging.Evaluation/ExpressionEvaluator.cs | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ArrayValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ArrayValueReference.cs index 0d2c4109e..b20e5095b 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ArrayValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ArrayValueReference.cs @@ -44,7 +44,11 @@ public ArrayValueReference (EvaluationContext ctx, object arr, int[] indices) : public override object Value { get { - return adaptor.GetElement (indices); + try { + return adaptor.GetElement (indices); + } catch (Exception e) { + throw new EvaluatorException (string.Format ("Failed to get array element: {0}", e.Message), e); + } } set { adaptor.SetElement (indices, value); diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs index 03ee28739..dc894e57c 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ExpressionEvaluator.cs @@ -208,6 +208,10 @@ protected EvaluatorException (SerializationInfo info, StreamingContext context) public EvaluatorException (string msg, params object[] args): base (string.Format(msg, args)) { } + + public EvaluatorException (string message, Exception innerException) : base (message, innerException) + { + } } [Serializable] From 088786ff85d0bf25adc0187fca62aaa03dedc2f0 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Fri, 10 Feb 2017 23:18:24 +0300 Subject: [PATCH 25/26] Safe modifications in SetRawValue (cherry picked from commit 2e51f2e) --- .../ArrayElementGroup.cs | 3 +-- .../ValueModificationUtil.cs | 20 ++++++++++++++----- .../ValueReference.cs | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs index 126ffee5b..e3039ada4 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ArrayElementGroup.cs @@ -329,8 +329,7 @@ public void SetRawValue (ObjectPath path, object value, EvaluationOptions option int[] idx = StringToIndices (path [1]); EvaluationContext cctx = ctx.WithOptions (options); - object val = cctx.Adapter.FromRawValue (cctx, value); - array.SetElement (idx, val); + ValueModificationUtil.ModifyValueFromRaw (cctx, value, val => array.SetElement (idx, val)); } } diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs index a9e2024a9..54763ccdc 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueModificationUtil.cs @@ -36,15 +36,25 @@ internal static EvaluationResult ModifyValue (EvaluationContext context, string throw new ValueModificationException(string.Format ("Cannot get real object of {0}", value), e); } var convertedValue = ConvertRightHandValue (context, val, expectedType); - try { - valueSetter (convertedValue); - } catch (Exception e) { - throw new ValueModificationException(string.Format ("Error while assigning new value to object: {0}", e.Message), e); - } + ModifyValue (convertedValue, valueSetter); // don't wrap with try-catch it, this call normally should not throw exceptions produced by wrong user input. // If exception was occured this means something has gone wrong in we have to report it in log return context.Evaluator.TargetObjectToExpression (context, convertedValue); } + internal static void ModifyValueFromRaw (EvaluationContext context, object rawValue, Action valueSetter) + { + object val = context.Adapter.FromRawValue (context, rawValue); + ModifyValue (val, valueSetter); + } + + static void ModifyValue (object value, Action valueSetter) + { + try { + valueSetter (value); + } catch (Exception e) { + throw new ValueModificationException (string.Format ("Error while assigning new value to object: {0}", e.Message), e); + } + } } } \ No newline at end of file diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs index aea1a3aa9..bb1c8404f 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs @@ -169,7 +169,7 @@ protected virtual void SetRawValue (ObjectPath path, object value, EvaluationOpt { var ctx = GetContext (options); - SetValue (ctx, Context.Adapter.FromRawValue (ctx, value)); + ValueModificationUtil.ModifyValueFromRaw (ctx, value, val => SetValue (ctx, val)); } ObjectValue[] IObjectValueSource.GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) From e6f8fb01bdef807d9dac264767ffa57277202cd2 Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Sat, 11 Feb 2017 01:59:06 +0300 Subject: [PATCH 26/26] Diagnostics in SetValue() of NullValueReference and LiteralValueReference (cherry picked from commit 31019a1) --- .../Mono.Debugging.Evaluation/LiteralValueReference.cs | 2 +- Mono.Debugging/Mono.Debugging.Evaluation/NullValueReference.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/LiteralValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/LiteralValueReference.cs index d4d76f0da..74a54beb3 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/LiteralValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/LiteralValueReference.cs @@ -108,7 +108,7 @@ public override object Value { return value; } set { - throw new NotSupportedException (); + throw new NotSupportedException ("Assignment to a literal is not possible"); } } diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/NullValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/NullValueReference.cs index e6f7f359f..271af726d 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/NullValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/NullValueReference.cs @@ -50,7 +50,7 @@ public override object Value { return obj; } set { - throw new NotSupportedException (); + throw new NotSupportedException ("Assignment to the null literal is not possible"); } }