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()