Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 12 additions & 16 deletions src/MIDebugEngine/AD7.Impl/AD7Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
96 changes: 96 additions & 0 deletions src/MIDebugEngine/Engine.Impl/OperationThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ private class OperationDescriptor
/// Delegate that was added via 'RunOperation'. Is of type 'Operation' or 'AsyncOperation'
/// </summary>
public readonly Delegate Target;

/// <summary>
/// Handler invoked on the worker thread if the operation faults. Only set for PostAsyncOperation.
/// </summary>
public Action<Exception> ErrorHandler;
Comment on lines +41 to +44

public ExceptionDispatchInfo ExceptionDispatchInfo;
public Task Task;
private bool _isStarted;
Expand Down Expand Up @@ -121,6 +127,20 @@ public void RunOperation(string text, CancellationTokenSource canTokenSource, As
SetOperationInternalWithProgress(op, text, canTokenSource);
}

/// <summary>
/// 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
/// <paramref name="onError"/>.
/// </summary>
public void PostAsyncOperation(AsyncOperation op, Action<Exception> onError)
{
if (op == null)
throw new ArgumentNullException(nameof(op));
if (onError == null)
throw new ArgumentNullException(nameof(onError));

PostAsyncOperationInternal(op, onError);
}

public void Close()
{
Expand Down Expand Up @@ -189,6 +209,27 @@ internal void SetOperationInternalWithProgress(AsyncProgressOperation op, string
}
}
}

internal void PostAsyncOperationInternal(AsyncOperation op, Action<Exception> 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)
Expand Down Expand Up @@ -276,7 +317,28 @@ private bool TrySetOperationInternalWithProgress(AsyncProgressOperation op, stri
return false;
}

private bool TryPostAsyncOperationInternal(AsyncOperation op, Action<Exception> 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()
Expand Down Expand Up @@ -333,11 +395,20 @@ private void ThreadFunc()

if (!completeAsync)
{
// Capture the fault before clearing the slot so a synchronous throw is still reported.
Action<Exception> 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);
}
}
}

Expand Down Expand Up @@ -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<Exception> errorHandler = _runningOp.ErrorHandler;
ExceptionDispatchInfo exceptionDispatchInfo = _runningOp.ExceptionDispatchInfo;

_runningOp = null;
_runningOpCompleteEvent.Set();

if (errorHandler != null && exceptionDispatchInfo != null)
{
InvokeErrorHandler(errorHandler, exceptionDispatchInfo.SourceException);
}
Comment on lines +471 to +474
}

private void InvokeErrorHandler(Action<Exception> 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()
Expand Down
Loading