diff --git a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs index ffb8146b8..65dcf5681 100755 --- a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs @@ -319,28 +319,24 @@ public int ContinueFromSynchronousEvent(IDebugEvent2 eventObject) { if (eventObject is AD7ProgramCreateEvent) { - Exception exception = null; - try { _engineCallback.OnLoadComplete(); - // At this point breakpoints and exception settings have been sent down, so we can resume the target - _pollThread.RunOperation(() => - { - return _debuggedProcess.ResumeFromLaunch(); - }); + + // Resume the target on the worker thread without blocking the UI thread this runs on. + // Resume faults are reported via onError, since the SDM drops errors returned from here. + _pollThread.PostAsyncOperation( + () => _debuggedProcess.ResumeFromLaunch(), + (exception) => + { + SendStartDebuggingError(exception); + _debuggedProcess.Terminate(); + }); } catch (Exception e) { - exception = e; - // Return from the catch block so that we can let the exception unwind - the stack can get kind of big - } - - if (exception != null) - { - // If something goes wrong, report the error and then stop debugging. The SDM will drop errors - // from ContinueFromSynchronousEvent, so we want to deal with them ourself. - SendStartDebuggingError(exception); + // Report synchronous failures ourselves, since the SDM drops errors returned from here. + SendStartDebuggingError(e); _debuggedProcess.Terminate(); } diff --git a/src/MIDebugEngine/Engine.Impl/OperationThread.cs b/src/MIDebugEngine/Engine.Impl/OperationThread.cs index 2c5d71a24..b59843c20 100755 --- a/src/MIDebugEngine/Engine.Impl/OperationThread.cs +++ b/src/MIDebugEngine/Engine.Impl/OperationThread.cs @@ -37,6 +37,12 @@ private class OperationDescriptor /// Delegate that was added via 'RunOperation'. Is of type 'Operation' or 'AsyncOperation' /// public readonly Delegate Target; + + /// + /// Handler invoked on the worker thread if the operation faults. Only set for PostAsyncOperation. + /// + public Action ErrorHandler; + public ExceptionDispatchInfo ExceptionDispatchInfo; public Task Task; private bool _isStarted; @@ -121,6 +127,20 @@ public void RunOperation(string text, CancellationTokenSource canTokenSource, As SetOperationInternalWithProgress(op, text, canTokenSource); } + /// + /// Send an async operation to the worker thread, claiming the running-op slot but returning without + /// waiting for it to complete. Later operations still serialize behind it. Faults are reported to + /// . + /// + public void PostAsyncOperation(AsyncOperation op, Action onError) + { + if (op == null) + throw new ArgumentNullException(nameof(op)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + PostAsyncOperationInternal(op, onError); + } public void Close() { @@ -189,6 +209,27 @@ internal void SetOperationInternalWithProgress(AsyncProgressOperation op, string } } } + + internal void PostAsyncOperationInternal(AsyncOperation op, Action onError) + { + // If this is called on the Worker thread it will deadlock + Debug.Assert(!IsPollThread()); + + while (true) + { + if (_isClosed) + throw new ObjectDisposedException("WorkerThread"); + + // Wait for the slot so this serializes behind any in-flight operation. + _runningOpCompleteEvent.WaitOne(); + + if (TryPostAsyncOperationInternal(op, onError)) + { + return; + } + } + } + public void PostOperation(Operation op) { if (op == null) @@ -276,7 +317,28 @@ private bool TrySetOperationInternalWithProgress(AsyncProgressOperation op, stri return false; } + private bool TryPostAsyncOperationInternal(AsyncOperation op, Action onError) + { + lock (_eventLock) + { + if (_isClosed) + throw new ObjectDisposedException("WorkerThread"); + + if (_runningOp == null) + { + _runningOpCompleteEvent.Reset(); + + _runningOp = new OperationDescriptor(op) { ErrorHandler = onError }; + + _opSet.Set(); + // Unlike TrySetOperationInternal, do not wait for completion; faults are routed to onError. + return true; + } + } + + return false; + } // Thread routine for the poll loop. It handles calls coming in from the debug engine as well as polling for debug events. private void ThreadFunc() @@ -333,11 +395,20 @@ private void ThreadFunc() if (!completeAsync) { + // Capture the fault before clearing the slot so a synchronous throw is still reported. + Action errorHandler = runningOp.ErrorHandler; + ExceptionDispatchInfo exceptionDispatchInfo = runningOp.ExceptionDispatchInfo; + runningOp.MarkComplete(); Debug.Assert(_runningOp == runningOp, "How did m_runningOp change?"); _runningOp = null; _runningOpCompleteEvent.Set(); + + if (errorHandler != null && exceptionDispatchInfo != null) + { + InvokeErrorHandler(errorHandler, exceptionDispatchInfo.SourceException); + } } } @@ -389,8 +460,33 @@ internal void OnAsyncRunningOpComplete(Task t) } } _runningOp.MarkComplete(); + + // Capture the fault before clearing the slot so it is routed to the handler, not discarded. + Action errorHandler = _runningOp.ErrorHandler; + ExceptionDispatchInfo exceptionDispatchInfo = _runningOp.ExceptionDispatchInfo; + _runningOp = null; _runningOpCompleteEvent.Set(); + + if (errorHandler != null && exceptionDispatchInfo != null) + { + InvokeErrorHandler(errorHandler, exceptionDispatchInfo.SourceException); + } + } + + private void InvokeErrorHandler(Action errorHandler, Exception exception) + { + try + { + errorHandler(exception); + } + catch (Exception e) when (ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: false)) + { + if (PostedOperationErrorEvent != null) + { + PostedOperationErrorEvent(this, e); + } + } } internal bool IsPollThread()