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 @@
// </file> // </file>
using System; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@ -13,82 +14,134 @@ using System.Windows.Forms;
namespace Debugger namespace Debugger
{ {
delegate object MethodInvokerWithReturnValue(); public delegate T MethodInvokerWithReturnValue<T>();
class MTA2STA public enum CallMethod {DirectCall, Manual, HiddenForm, HiddenFormWithTimeout};
public class MTA2STA
{ {
Form hiddenForm; Form hiddenForm;
IntPtr hiddenFormHandle; 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() public MTA2STA()
{ {
targetThread = System.Threading.Thread.CurrentThread;
hiddenForm = new Form(); hiddenForm = new Form();
// Force handle creation // Force handle creation
hiddenFormHandle = hiddenForm.Handle; hiddenFormHandle = hiddenForm.Handle;
} }
static void TraceMsg(string msg)
{
//System.Console.WriteLine("MTA2STA: " + msg);
}
/// <summary> /// <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> /// </summary>
public void SoftWait(WaitHandle waitFor) public int SoftWait(params WaitHandle[] waitFor)
{ {
if (System.Threading.Thread.CurrentThread.GetApartmentState() == System.Threading.ApartmentState.STA) { List<WaitHandle> waits = new List<WaitHandle> (waitFor);
staPump.Set(); waits.Add(pendingCallsNotEmpty);
// Wait until the waitFor handle is set while(true) {
while(WaitHandle.WaitAny(new WaitHandle[] {staPump, waitFor}) != 1) { int i = WaitHandle.WaitAny(waits.ToArray());
Application.DoEvents(); 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 /// <summary>
public object CallInSTA(object targetObject, string functionName, object[] functionParameters) /// 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; Call(callDelegate, false);
CallInSTA(delegate { returnValue = callDelegate(); }, false);
return returnValue;
} }
void CallInSTA(MethodInvoker callDelegate, bool mayAbandon) void Call(MethodInvoker callDelegate, bool hasReturnValue)
{ {
if (hiddenForm.InvokeRequired == true) { // Enqueue the call
// Warrning: BeginInvoke will not pass exceptions if you do not use MethodInvoker delegate! WaitHandle callDone = EnqueueCall(callDelegate);
IAsyncResult async = hiddenForm.BeginInvoke(callDelegate);
// Pump a locked STA thread if (targetThread == System.Threading.Thread.CurrentThread) {
staPump.Set(); PerformAllCalls();
// Give it 1 second to run return;
if (!async.AsyncWaitHandle.WaitOne(1000, true)) { }
// Abandon the call if possible
if (mayAbandon) { // We have the call waiting in queue, we need to call it (not waiting for it to finish)
System.Console.WriteLine("Callback time out! Unleashing thread."); switch (callMethod) {
} else { case CallMethod.DirectCall:
System.Console.WriteLine("Warring: Call in STA is taking too long"); PerformAllCalls();
hiddenForm.EndInvoke(async); // Keep waiting 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 { } else {
callDelegate(); callDone.WaitOne();
} }
} }
public static object MarshalParamTo(object param, Type outputType) public static object MarshalParamTo(object param, Type outputType)
{ {
if (param is IntPtr) { if (param is IntPtr) {
@ -144,7 +197,6 @@ namespace Debugger
convertedParams[i] = MarshalParamTo(functionParameters[i], methodParamsInfo[i].ParameterType); convertedParams[i] = MarshalParamTo(functionParameters[i], methodParamsInfo[i].ParameterType);
} }
TraceMsg ("Invoking " + functionName + "...");
try { try {
if (targetObject is Type) { if (targetObject is Type) {
return method.Invoke(null, convertedParams); return method.Invoke(null, convertedParams);

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

@ -42,11 +42,7 @@ namespace Debugger
void Call(MethodInvoker callback) void Call(MethodInvoker callback)
{ {
if (debugger.RequiredApartmentState == ApartmentState.STA) { debugger.MTA2STA.Call(callback);
debugger.MTA2STA.CallInSTA(callback);
} else {
callback();
}
} }
public void StepComplete(System.IntPtr pAppDomain, System.IntPtr pThread, System.IntPtr pStepper, Debugger.Interop.CorDebug.CorDebugStepReason reason) 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
{ {
PausedReason? pausedReason = null; PausedReason? pausedReason = null;
bool pauseOnHandledException = false; bool pauseOnHandledException = false;
EventWaitHandle waitForPauseHandle = new EventWaitHandle(false, EventResetMode.ManualReset); ManualResetEvent pausedHandle = new ManualResetEvent(false);
object sessionID = new object(); object sessionID = new object();
object debugeeStateID = new object(); object debugeeStateID = new object();
@ -181,7 +181,7 @@ namespace Debugger
} }
if (IsPaused) { if (IsPaused) {
waitForPauseHandle.Set(); pausedHandle.Set();
} }
} }
@ -201,7 +201,8 @@ namespace Debugger
} }
OnDebuggingResumed(); OnDebuggingResumed();
waitForPauseHandle.Reset();
pausedHandle.Reset();
pausedReason = null; pausedReason = null;
@ -220,16 +221,8 @@ namespace Debugger
/// </summary> /// </summary>
public void WaitForPause() public void WaitForPause()
{ {
if (IsRunning) { if (this.MTA2STA.SoftWait(PausedHandle, noProcessesHandle) == 1) {
EventHandler<ProcessEventArgs> throwError = delegate { throw new DebuggerException("Process exited before pausing");
if (Processes.Count == 0) {
throw new DebuggerException("Process exited before pausing");
}
};
this.ProcessExited += throwError;
throwError(null, null);
this.MTA2STA.SoftWait(WaitForPauseHandle);
this.ProcessExited -= throwError;
} }
} }
@ -238,24 +231,15 @@ namespace Debugger
/// </summary> /// </summary>
public void WaitForPrecessExit() public void WaitForPrecessExit()
{ {
// The process is removed first and then the even is called this.MTA2STA.SoftWait(noProcessesHandle);
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);
}
} }
/// <summary> /// <summary>
/// Wait handle, which will be set as long as the debugger is paused /// Wait handle, which will be set as long as the debugger is paused
/// </summary> /// </summary>
public WaitHandle WaitForPauseHandle { public WaitHandle PausedHandle {
get { get {
return waitForPauseHandle; return pausedHandle;
} }
} }

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

@ -23,20 +23,13 @@ namespace Debugger
ManagedCallback managedCallback; ManagedCallback managedCallback;
ManagedCallbackProxy managedCallbackProxy; ManagedCallbackProxy managedCallbackProxy;
ApartmentState requiredApartmentState;
MTA2STA mta2sta = new MTA2STA(); MTA2STA mta2sta = new MTA2STA();
VariableCollection localVariables; VariableCollection localVariables;
string debuggeeVersion; string debuggeeVersion;
public ApartmentState RequiredApartmentState { public MTA2STA MTA2STA {
get {
return requiredApartmentState;
}
}
internal MTA2STA MTA2STA {
get { get {
return mta2sta; return mta2sta;
} }
@ -62,7 +55,11 @@ namespace Debugger
public NDebugger() 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; this.ModuleLoaded += SetBreakpointsInModule;
@ -135,7 +132,7 @@ namespace Debugger
TraceMessage("Reset done"); TraceMessage("Reset done");
corDebug.Terminate(); //corDebug.Terminate();
TraceMessage("ICorDebug terminated"); TraceMessage("ICorDebug terminated");
} }

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

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

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

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

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

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

Loading…
Cancel
Save