Browse Source

Rewriten MTA2STA so that it can be controlled more in MTA and tests

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@856 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
David Srbecký 20 years ago
parent
commit
97b0816212
  1. 140
      src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/MTA2STA.cs
  2. 6
      src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/ManagedCallbackProxy.cs
  3. 34
      src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/NDebugger-StateControl.cs
  4. 17
      src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/NDebugger.cs
  5. 8
      src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/NDebugger-Processes.cs
  6. 12
      src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Process.cs
  7. 3
      src/AddIns/Misc/Debugger/Debugger.Tests/Project/Src/DebuggerTests.cs

140
src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/MTA2STA.cs

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
// </file>
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
@ -13,82 +14,134 @@ using System.Windows.Forms; @@ -13,82 +14,134 @@ using System.Windows.Forms;
namespace Debugger
{
delegate object MethodInvokerWithReturnValue();
public delegate T MethodInvokerWithReturnValue<T>();
class MTA2STA
public enum CallMethod {DirectCall, Manual, HiddenForm, HiddenFormWithTimeout};
public class MTA2STA
{
Form hiddenForm;
IntPtr hiddenFormHandle;
AutoResetEvent staPump = new AutoResetEvent(false);
System.Threading.Thread targetThread;
CallMethod callMethod = CallMethod.HiddenFormWithTimeout;
Queue<MethodInvoker> pendingCalls = new Queue<MethodInvoker>();
ManualResetEvent pendingCallsNotEmpty = new ManualResetEvent(false);
WaitHandle EnqueueCall(MethodInvoker callDelegate)
{
lock (pendingCalls) {
ManualResetEvent callDone = new ManualResetEvent(false);
pendingCalls.Enqueue(delegate{
callDelegate();
callDone.Set();
});
pendingCallsNotEmpty.Set();
return callDone;
}
}
void PerformAllCalls()
{
lock (pendingCalls) {
while (pendingCalls.Count > 0) {
pendingCalls.Dequeue()();
}
pendingCallsNotEmpty.Reset();
}
}
public CallMethod CallMethod {
get {
return callMethod;
}
set {
callMethod = value;
}
}
public MTA2STA()
{
targetThread = System.Threading.Thread.CurrentThread;
hiddenForm = new Form();
// Force handle creation
hiddenFormHandle = hiddenForm.Handle;
}
static void TraceMsg(string msg)
{
//System.Console.WriteLine("MTA2STA: " + msg);
}
/// <summary>
/// SoftWait waits for the given WaitHandle and allows processing of CallInSTA during the wait
/// SoftWait waits for any of the given WaitHandles and allows processing of calls during the wait
/// </summary>
public void SoftWait(WaitHandle waitFor)
public int SoftWait(params WaitHandle[] waitFor)
{
if (System.Threading.Thread.CurrentThread.GetApartmentState() == System.Threading.ApartmentState.STA) {
staPump.Set();
// Wait until the waitFor handle is set
while(WaitHandle.WaitAny(new WaitHandle[] {staPump, waitFor}) != 1) {
Application.DoEvents();
List<WaitHandle> waits = new List<WaitHandle> (waitFor);
waits.Add(pendingCallsNotEmpty);
while(true) {
int i = WaitHandle.WaitAny(waits.ToArray());
PerformAllCalls();
if (i < waits.Count - 1) { // If not pendingCallsNotEmpty
return i;
}
} else {
waitFor.WaitOne();
}
}
// Try to avoid this since it will catch exceptions and it is slow
public object CallInSTA(object targetObject, string functionName, object[] functionParameters)
/// <summary>
/// Performs all waiting calls on the current thread
/// </summary>
public void Pulse()
{
return CallInSTA(delegate { return InvokeMethod(targetObject, functionName, functionParameters); });
PerformAllCalls();
}
public void CallInSTA(MethodInvoker callDelegate)
public T Call<T>(MethodInvokerWithReturnValue<T> callDelegate)
{
CallInSTA(callDelegate, true);
T returnValue = default(T);
Call(delegate { returnValue = callDelegate(); }, true);
return returnValue;
}
public object CallInSTA(MethodInvokerWithReturnValue callDelegate)
public void Call(MethodInvoker callDelegate)
{
object returnValue = null;
CallInSTA(delegate { returnValue = callDelegate(); }, false);
return returnValue;
Call(callDelegate, false);
}
void CallInSTA(MethodInvoker callDelegate, bool mayAbandon)
void Call(MethodInvoker callDelegate, bool hasReturnValue)
{
if (hiddenForm.InvokeRequired == true) {
// Warrning: BeginInvoke will not pass exceptions if you do not use MethodInvoker delegate!
IAsyncResult async = hiddenForm.BeginInvoke(callDelegate);
// Pump a locked STA thread
staPump.Set();
// Give it 1 second to run
if (!async.AsyncWaitHandle.WaitOne(1000, true)) {
// Abandon the call if possible
if (mayAbandon) {
System.Console.WriteLine("Callback time out! Unleashing thread.");
} else {
System.Console.WriteLine("Warring: Call in STA is taking too long");
hiddenForm.EndInvoke(async); // Keep waiting
}
// Enqueue the call
WaitHandle callDone = EnqueueCall(callDelegate);
if (targetThread == System.Threading.Thread.CurrentThread) {
PerformAllCalls();
return;
}
// We have the call waiting in queue, we need to call it (not waiting for it to finish)
switch (callMethod) {
case CallMethod.DirectCall:
PerformAllCalls();
break;
case CallMethod.Manual:
// Nothing we can do - someone else must call SoftWait or Pulse
break;
case CallMethod.HiddenForm:
case CallMethod.HiddenFormWithTimeout:
hiddenForm.BeginInvoke((MethodInvoker)PerformAllCalls);
break;
}
// Wait for the call to finish
if (!hasReturnValue && callMethod == CallMethod.HiddenFormWithTimeout) {
// Give it 5 seconds to run
if (!callDone.WaitOne(5000, true)) {
System.Console.WriteLine("Call time out! Continuing...");
}
} else {
callDelegate();
callDone.WaitOne();
}
}
public static object MarshalParamTo(object param, Type outputType)
{
if (param is IntPtr) {
@ -144,7 +197,6 @@ namespace Debugger @@ -144,7 +197,6 @@ namespace Debugger
convertedParams[i] = MarshalParamTo(functionParameters[i], methodParamsInfo[i].ParameterType);
}
TraceMsg ("Invoking " + functionName + "...");
try {
if (targetObject is Type) {
return method.Invoke(null, convertedParams);

6
src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/ManagedCallbackProxy.cs

@ -42,11 +42,7 @@ namespace Debugger @@ -42,11 +42,7 @@ namespace Debugger
void Call(MethodInvoker callback)
{
if (debugger.RequiredApartmentState == ApartmentState.STA) {
debugger.MTA2STA.CallInSTA(callback);
} else {
callback();
}
debugger.MTA2STA.Call(callback);
}
public void StepComplete(System.IntPtr pAppDomain, System.IntPtr pThread, System.IntPtr pStepper, Debugger.Interop.CorDebug.CorDebugStepReason reason)

34
src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/NDebugger-StateControl.cs

@ -21,7 +21,7 @@ namespace Debugger @@ -21,7 +21,7 @@ namespace Debugger
{
PausedReason? pausedReason = null;
bool pauseOnHandledException = false;
EventWaitHandle waitForPauseHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
ManualResetEvent pausedHandle = new ManualResetEvent(false);
object sessionID = new object();
object debugeeStateID = new object();
@ -181,7 +181,7 @@ namespace Debugger @@ -181,7 +181,7 @@ namespace Debugger
}
if (IsPaused) {
waitForPauseHandle.Set();
pausedHandle.Set();
}
}
@ -201,7 +201,8 @@ namespace Debugger @@ -201,7 +201,8 @@ namespace Debugger
}
OnDebuggingResumed();
waitForPauseHandle.Reset();
pausedHandle.Reset();
pausedReason = null;
@ -220,16 +221,8 @@ namespace Debugger @@ -220,16 +221,8 @@ namespace Debugger
/// </summary>
public void WaitForPause()
{
if (IsRunning) {
EventHandler<ProcessEventArgs> throwError = delegate {
if (Processes.Count == 0) {
throw new DebuggerException("Process exited before pausing");
}
};
this.ProcessExited += throwError;
throwError(null, null);
this.MTA2STA.SoftWait(WaitForPauseHandle);
this.ProcessExited -= throwError;
if (this.MTA2STA.SoftWait(PausedHandle, noProcessesHandle) == 1) {
throw new DebuggerException("Process exited before pausing");
}
}
@ -238,24 +231,15 @@ namespace Debugger @@ -238,24 +231,15 @@ namespace Debugger
/// </summary>
public void WaitForPrecessExit()
{
// The process is removed first and then the even is called
AutoResetEvent exitedEvent = new AutoResetEvent(false);
this.ProcessExited += delegate {
exitedEvent.Set();
};
// If it has not been removed yet, we will get an event later
while (Processes.Count > 0) {
this.MTA2STA.SoftWait(exitedEvent);
}
this.MTA2STA.SoftWait(noProcessesHandle);
}
/// <summary>
/// Wait handle, which will be set as long as the debugger is paused
/// </summary>
public WaitHandle WaitForPauseHandle {
public WaitHandle PausedHandle {
get {
return waitForPauseHandle;
return pausedHandle;
}
}

17
src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/NDebugger.cs

@ -23,20 +23,13 @@ namespace Debugger @@ -23,20 +23,13 @@ namespace Debugger
ManagedCallback managedCallback;
ManagedCallbackProxy managedCallbackProxy;
ApartmentState requiredApartmentState;
MTA2STA mta2sta = new MTA2STA();
VariableCollection localVariables;
string debuggeeVersion;
public ApartmentState RequiredApartmentState {
get {
return requiredApartmentState;
}
}
internal MTA2STA MTA2STA {
public MTA2STA MTA2STA {
get {
return mta2sta;
}
@ -62,7 +55,11 @@ namespace Debugger @@ -62,7 +55,11 @@ namespace Debugger
public NDebugger()
{
requiredApartmentState = System.Threading.Thread.CurrentThread.GetApartmentState();
if (ApartmentState.STA == System.Threading.Thread.CurrentThread.GetApartmentState()) {
mta2sta.CallMethod = CallMethod.HiddenFormWithTimeout;
} else {
mta2sta.CallMethod = CallMethod.DirectCall;
}
this.ModuleLoaded += SetBreakpointsInModule;
@ -135,7 +132,7 @@ namespace Debugger @@ -135,7 +132,7 @@ namespace Debugger
TraceMessage("Reset done");
corDebug.Terminate();
//corDebug.Terminate();
TraceMessage("ICorDebug terminated");
}

8
src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/NDebugger-Processes.cs

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Debugger.Interop.CorDebug;
namespace Debugger
@ -15,6 +16,9 @@ namespace Debugger @@ -15,6 +16,9 @@ namespace Debugger
public partial class NDebugger
{
List<Process> processCollection = new List<Process>();
// Is set as long as the process count is zero
ManualResetEvent noProcessesHandle = new ManualResetEvent(true);
public event EventHandler<ProcessEventArgs> ProcessStarted;
public event EventHandler<ProcessEventArgs> ProcessExited;
@ -39,12 +43,16 @@ namespace Debugger @@ -39,12 +43,16 @@ namespace Debugger
{
processCollection.Add(process);
OnProcessStarted(process);
noProcessesHandle.Reset();
}
internal void RemoveProcess(Process process)
{
processCollection.Remove(process);
OnProcessExited(process);
if (processCollection.Count == 0) {
noProcessesHandle.Set();
}
}
protected virtual void OnProcessStarted(Process process)

12
src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Process.cs

@ -78,16 +78,12 @@ namespace Debugger @@ -78,16 +78,12 @@ namespace Debugger
static public Process CreateProcess(NDebugger debugger, string filename, string workingDirectory, string arguments)
{
Process createdProcess = null;
if (debugger.RequiredApartmentState == ApartmentState.STA) {
createdProcess = (Process)debugger.MTA2STA.CallInSTA(typeof(Process), "StartInternal", new Object[] {debugger, filename, workingDirectory, arguments});
} else {
createdProcess = StartInternal(debugger, filename, workingDirectory, arguments);
}
return createdProcess;
return debugger.MTA2STA.Call<Process>(delegate{
return StartInternal(debugger, filename, workingDirectory, arguments);
});
}
static public unsafe Process StartInternal(NDebugger debugger, string filename, string workingDirectory, string arguments)
static unsafe Process StartInternal(NDebugger debugger, string filename, string workingDirectory, string arguments)
{
debugger.TraceMessage("Executing " + filename);

3
src/AddIns/Misc/Debugger/Debugger.Tests/Project/Src/DebuggerTests.cs

@ -31,6 +31,7 @@ namespace Debugger.Tests @@ -31,6 +31,7 @@ namespace Debugger.Tests
public DebuggerTests()
{
debugger = new NDebugger();
debugger.MTA2STA.CallMethod = CallMethod.Manual;
debugger.LogMessage += delegate(object sender, MessageEventArgs e) {
log += e.Message;
lastLogMessage = e.Message;
@ -62,7 +63,7 @@ namespace Debugger.Tests @@ -62,7 +63,7 @@ namespace Debugger.Tests
Assert.AreEqual("Hello world!\r\n", log);
}
[Test, Ignore("Deadlocks")]
[Test, Ignore("Does not work for first time")]
public void Breakpoint()
{
debugger.AddBreakpoint("Breakpoint.cs", 16);

Loading…
Cancel
Save