diff --git a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs index 750114d02..7369f87c1 100644 --- a/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs +++ b/Mono.Debugging.Soft/SoftDebuggerAdaptor.cs @@ -32,8 +32,7 @@ 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; using Mono.Debugging.Evaluation; @@ -2145,100 +2144,112 @@ 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; + } - readonly ManualResetEvent shutdownEvent = new ManualResetEvent (false); + public Value[] OutArgs { get; private set; } + } + + 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; } } public override string Description { - get { - return function.DeclaringType.FullName + "." + function.Name; - } - } - - public override void Invoke () - { - 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); + get + { + try { + return function.DeclaringType.FullName + "." + function.Name; + } + catch (Exception e) { + DebuggerLoggingService.LogError ("Exception during getting description of method", e); + return "[Unknown method]"; } - 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 () { 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++; + var tcs = new TaskCompletionSource> (); + invokeAsyncResult = (IInvokeAsyncResult)obj.BeginInvokeMethod (ctx.Thread, function, args, optionsToInvoke, ar => { + try { + if (Token.IsCancellationRequested) { + tcs.SetCanceled (); + return; + } + var endInvokeResult = obj.EndInvokeMethodWithResult (ar); + tcs.SetResult (new SoftOperationResult (endInvokeResult.Result, false, endInvokeResult.OutArgs)); + } + catch (InvocationException ex) { + 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 (CommandException e) { + if (e.ErrorCode == ErrorCode.INVOKE_ABORTED) { + tcs.TrySetCanceled (); + } + else { + tcs.SetException (new EvaluatorException (e.Message)); + } + } + catch (Exception 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 (string.Format ("Unexpected exception has thrown when ending invocation of {0}", GetInfo ()), e); + tcs.SetException (e); + } + } + finally { + UpdateSessionState (); + } + }, null); + return tcs.Task; + } catch (Exception e) { + UpdateSessionState (); + DebuggerLoggingService.LogError (string.Format ("Unexpected exception has thrown when invoking {0}", GetInfo ()), e); + throw; } } - + string GetInfo () { try { @@ -2250,41 +2261,26 @@ string GetInfo () 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); + function.FullName, + type == null? "[null]" : type.FullName); } catch (Exception ex) { DebuggerLoggingService.LogError ("Error getting info for SDB MethodCall", ex); - return ""; + return "[Unknown method]"; } } - 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; - } - - public Value ReturnValue { - get { - if (exception != null) - throw new EvaluatorException (exception.Message); - return result.Result; - } + ctx.Session.StackVersion++; } - public Value[] OutArgs { - get { - if (exception != null) - throw new EvaluatorException (exception.Message); - return result.OutArgs; + protected override void AbortImpl (int abortCallTimes) + { + 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.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..f6b010014 100644 --- a/Mono.Debugging.Win32/CorMethodCall.cs +++ b/Mono.Debugging.Win32/CorMethodCall.cs @@ -1,47 +1,143 @@ -using System.Threading; +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; 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 override string Description + public CorMethodCall (CorEvaluationContext context, CorFunction function, CorType[] typeArgs, CorValue[] args) { - get { return OnGetDescription (); } + this.context = context; + this.function = function; + this.typeArgs = typeArgs; + this.args = args; + eval = context.Eval; } - public override void Invoke ( ) + void ProcessOnEvalComplete (object sender, CorEvalEventArgs evalArgs) { - OnInvoke (); + DoProcessEvalFinished (evalArgs, false); } - public override void Abort ( ) + void ProcessOnEvalException (object sender, CorEvalEventArgs evalArgs) { - OnAbort (); + DoProcessEvalFinished (evalArgs, true); } - public override void Shutdown ( ) + void DoProcessEvalFinished (CorEvalEventArgs evalArgs, bool isException) { - try { - Abort (); + if (evalArgs.Eval != eval) + return; + context.Session.OnEndEvaluating (); + evalArgs.Continue = false; + if (Token.IsCancellationRequested) { + DebuggerLoggingService.LogMessage ("EvalFinished() but evaluation was cancelled"); + tcs.TrySetCanceled (); } - catch { + else { + DebuggerLoggingService.LogMessage ("EvalFinished(). Setting the result"); + tcs.TrySetResult(new OperationResult (evalArgs.Eval.Result, isException)); } - DoneEvent.Set (); } - public override bool WaitForCompleted (int timeout) + void SubscribeOnEvals () + { + context.Session.Process.OnEvalComplete += ProcessOnEvalComplete; + context.Session.Process.OnEvalException += ProcessOnEvalException; + } + + void UnSubcribeOnEvals () { - return DoneEvent.WaitOne (timeout, false); + context.Session.Process.OnEvalComplete -= ProcessOnEvalComplete; + context.Session.Process.OnEvalException -= ProcessOnEvalException; + } + + public override string Description + { + get + { + var met = function.GetMethodInfo (context.Session); + if (met == null) + return "[Unknown method]"; + if (met.DeclaringType == null) + return met.Name; + return met.DeclaringType.FullName + "." + met.Name; + } + } + + readonly TaskCompletionSource> tcs = new TaskCompletionSource> (); + + protected override Task> InvokeAsyncImpl () + { + 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 + return Task.ContinueWith (task => { + UnSubcribeOnEvals (); + return task.Result; + }); + } + + + protected override void AbortImpl (int abortCallTimes) + { + 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; + } + tcs.SetException (e); + throw; + } } } } diff --git a/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs b/Mono.Debugging/Mono.Debugging.Client/ObjectValue.cs index a71c2f718..366150b21 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; @@ -530,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/AsyncOperationBase.cs b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs new file mode 100644 index 000000000..7330555a9 --- /dev/null +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationBase.cs @@ -0,0 +1,97 @@ +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; } + bool AbortCalled { get; } + void Abort (); + } + + public abstract class AsyncOperationBase : IAsyncOperationBase + { + public Task> Task { get; protected set; } + + public Task RawTask + { + get + { + return Task; + } + } + + public abstract string Description { get; } + + 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 bool AbortCalled { get { return Token.IsCancellationRequested; } } + + public void Abort () + { + try { + tokenSource.Cancel(); + AbortImpl (Interlocked.Increment (ref abortCalls) - 1); + } + catch (OperationCanceledException) { + // if CancelImpl throw OCE we shouldn't mute it + throw; + } + catch (Exception e) { + DebuggerLoggingService.LogMessage ("Exception in CancelImpl(): {0}", e.Message); + } + } + + public Task> InvokeAsync () + { + if (Task != null) throw new Exception("Task must be null"); + Task = InvokeAsyncImpl (); + return Task; + } + + 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 ea69b7424..162b9d47e 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/AsyncOperationManager.cs @@ -27,215 +27,176 @@ using System; using System.Collections.Generic; -using ST = System.Threading; +using System.Linq; +using System.Threading.Tasks; using Mono.Debugging.Client; namespace Mono.Debugging.Evaluation { - public class AsyncOperationManager: IDisposable + public class AsyncOperationManager : IDisposable { - List operationsToCancel = new List (); - internal bool Disposing; + readonly HashSet currentOperations = new HashSet (); + bool disposed = false; + const int ShortCancelTimeout = 100; - public void Invoke (AsyncOperation methodCall, int timeout) + static bool IsOperationCancelledException (Exception e, int depth = 4) { - methodCall.Aborted = false; - methodCall.Manager = this; + if (e is OperationCanceledException) + return true; + var aggregateException = e as AggregateException; - lock (operationsToCancel) { - operationsToCancel.Add (methodCall); - methodCall.Invoke (); + 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); } - if (timeout > 0) { - if (!methodCall.WaitForCompleted (timeout)) { - bool wasAborted = methodCall.Aborted; - methodCall.InternalAbort (); - lock (operationsToCancel) { - operationsToCancel.Remove (methodCall); - ST.Monitor.PulseAll (operationsToCancel); + 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)); + 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, abortCalled); + } + 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 (operationsToCancel) { - operationsToCancel.Remove (methodCall); - ST.Monitor.PulseAll (operationsToCancel); - 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; } + finally { + lock (currentOperations) { + currentOperations.Remove (mc); + } + } + } - if (!string.IsNullOrEmpty (methodCall.ExceptionMessage)) { - throw new Exception (methodCall.ExceptionMessage); + + 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); } } - - public void Dispose () + + void WaitAfterCancel (IAsyncOperationBase op, bool onlyWait) { - Disposing = true; - lock (operationsToCancel) { - foreach (AsyncOperation op in operationsToCancel) { - op.InternalShutdown (); + 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; + } + if (!onlyWait) { + op.Abort (); + } + if (op.RawTask.Wait (ShortCancelTimeout)) + break; + } + } + finally { + ChangeBusyState (false, desc); } - operationsToCancel.Clear (); } } public void AbortAll () { - lock (operationsToCancel) { - 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 operation in operations) { + var taskDescription = 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; + var abortCalled = operation.AbortCalled; + // if abort was already called (in AbortAll/Dispose) don't call it again, just wait + if (!abortCalled) { + operation.Abort (); } - // 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; + if (wait) { + WaitAfterCancel (operation, abortCalled); } - } 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/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..f2cfafa8b 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) { @@ -588,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)); } } @@ -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. } } @@ -1324,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) @@ -1355,10 +1359,12 @@ 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) { - ctx.WriteDebuggerError (ex); + DebuggerLoggingService.LogError ("Exception in GetExpressionValue()", ex); return ObjectValue.CreateUnknown (exp); } } diff --git a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs index dc207ae17..4bcb58b69 100644 --- a/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs +++ b/Mono.Debugging/Mono.Debugging.Evaluation/ValueReference.cs @@ -106,10 +106,13 @@ 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) { - Context.WriteDebuggerError (ex); + } + catch (Exception ex) { + DebuggerLoggingService.LogError ("Exception in CreateObjectValue()", ex); return DC.ObjectValue.CreateUnknown (Name); } } diff --git a/Mono.Debugging/Mono.Debugging.csproj b/Mono.Debugging/Mono.Debugging.csproj index 1b1ff538b..db6904f53 100644 --- a/Mono.Debugging/Mono.Debugging.csproj +++ b/Mono.Debugging/Mono.Debugging.csproj @@ -82,6 +82,7 @@ + @@ -138,4 +139,4 @@ ICSharpCode.NRefactory.CSharp - + \ No newline at end of file