diff --git a/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs b/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs index 89e145afe8..185bb18c56 100644 --- a/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs +++ b/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs @@ -215,7 +215,7 @@ namespace ICSharpCode.UnitTesting TaskService.ClearExceptCommentTasks(); TaskService.InUpdate = false; - TestRunnerCategory.ClearText(); + TestRunnerCategory.ClearText(); ShowUnitTestsPad(); ShowOutputPad(); @@ -225,7 +225,7 @@ namespace ICSharpCode.UnitTesting OnBeforeRunTests(); } - + /// /// Brings output pad to the front. /// @@ -252,7 +252,7 @@ namespace ICSharpCode.UnitTesting } if (lineRef != null) { return new Task(Path.GetFullPath(lineRef.FileName), - message, lineRef.Column, lineRef.Line, taskType); + message, lineRef.Column, lineRef.Line, taskType); } return new Task(String.Empty, message, 0, 0, taskType); } @@ -297,7 +297,7 @@ namespace ICSharpCode.UnitTesting /// void OnBuildComplete(BuildResults results, IProject project, string namespaceFilter, IClass fixture, IMember test) { - if (results.ErrorCount == 0 && IsRunningTest) { + if (results.ErrorCount == 0 && IsRunningTest) { UnitTestApplicationStartHelper helper = new UnitTestApplicationStartHelper(); UnitTestingOptions options = new UnitTestingOptions(); @@ -422,7 +422,7 @@ namespace ICSharpCode.UnitTesting ProjectService.RaiseEventEndBuild(new BuildEventArgs(LastBuildResults)); } } - + public class RunTestInPadCommand : AbstractRunTestCommand { ProcessRunner runner; @@ -430,12 +430,13 @@ namespace ICSharpCode.UnitTesting public RunTestInPadCommand() { runner = new ProcessRunner(); + runner.LogStandardOutputAndError = false; runner.OutputLineReceived += OutputLineReceived; runner.ProcessExited += ProcessExited; } - + protected override void RunTests(UnitTestApplicationStartHelper helper) - { + { TestRunnerCategory.AppendLine(helper.GetCommandLine()); runner.Start(helper.UnitTestApplication, helper.GetArguments()); } @@ -456,7 +457,7 @@ namespace ICSharpCode.UnitTesting } void ProcessExited(object source, EventArgs e) - { + { WorkbenchSingleton.SafeThreadAsyncCall(TestsFinished); } @@ -472,7 +473,7 @@ namespace ICSharpCode.UnitTesting { if (DebuggerService.IsDebuggerLoaded && DebuggerService.CurrentDebugger.IsDebugging) { if (MessageService.AskQuestion("${res:XML.MainMenu.RunMenu.Compile.StopDebuggingQuestion}", - "${res:XML.MainMenu.RunMenu.Compile.StopDebuggingTitle}")) + "${res:XML.MainMenu.RunMenu.Compile.StopDebuggingTitle}")) { DebuggerService.CurrentDebugger.Stop(); base.Run(); @@ -481,7 +482,7 @@ namespace ICSharpCode.UnitTesting base.Run(); } } - + protected override void RunTests(UnitTestApplicationStartHelper helper) { bool running = false; diff --git a/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs b/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs index cd10c57e33..67fe1f3343 100644 --- a/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs +++ b/src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs @@ -256,6 +256,7 @@ namespace ICSharpCode.SharpDevelop.Commands try { if (tool.UseOutputPad) { ProcessRunner processRunner = new ProcessRunner(); + processRunner.LogStandardOutputAndError = false; processRunner.ProcessExited += ProcessExitEvent; processRunner.OutputLineReceived += process_OutputLineReceived; processRunner.ErrorLineReceived += process_OutputLineReceived; @@ -289,7 +290,7 @@ namespace ICSharpCode.SharpDevelop.Commands WorkbenchSingleton.SafeThreadAsyncCall( delegate { ProcessRunner p = (ProcessRunner)sender; - TaskService.BuildMessageViewCategory.AppendLine("${res:XML.MainMenu.ToolMenu.ExternalTools.ExitedWithCode} " + p.ExitCode); + TaskService.BuildMessageViewCategory.AppendLine(StringParser.Parse("${res:XML.MainMenu.ToolMenu.ExternalTools.ExitedWithCode} " + p.ExitCode)); p.Dispose(); }); } diff --git a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs index 6482048b76..f3ef079c00 100644 --- a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs +++ b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs @@ -7,7 +7,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; +using System.Text; using System.Threading; using System.Windows.Forms; @@ -56,15 +58,29 @@ namespace ICSharpCode.SharpDevelop.Gui return selectedCategory; } set { + WorkbenchSingleton.AssertMainThread(); if (selectedCategory != value) { selectedCategory = value; - textEditorControl.Text = (value < 0) ? "" : StringParser.Parse(messageCategories[value].Text); - //textEditorControl.Refresh(); + DisplayActiveCategory(); OnSelectedCategoryIndexChanged(EventArgs.Empty); } } } + void DisplayActiveCategory() + { + WorkbenchSingleton.DebugAssertMainThread(); + if (selectedCategory < 0) { + textEditorControl.Text = ""; + } else { + lock (messageCategories[selectedCategory].SyncRoot) { + // accessing a categories' text takes its lock - but we have to take locks in the same + // order as in the Append calls to prevent a deadlock + EnqueueAppend(new AppendCall(messageCategories[selectedCategory], messageCategories[selectedCategory].Text, true)); + } + } + } + public bool WordWrap { get { return properties.Get("WordWrap", true); @@ -136,19 +152,17 @@ namespace ICSharpCode.SharpDevelop.Gui SetWordWrap(); myPanel.ResumeLayout(false); - SetText(messageCategories[selectedCategory], messageCategories[selectedCategory].Text); + DisplayActiveCategory(); ProjectService.SolutionLoaded += SolutionLoaded; } void SolutionLoaded(object sender, SolutionEventArgs e) { - foreach (MessageViewCategory category in messageCategories) - { - ClearText(category); + foreach (MessageViewCategory category in messageCategories) { + category.ClearText(); } } - void SetWordWrap() { bool wordWrap = this.WordWrap; @@ -179,105 +193,111 @@ namespace ICSharpCode.SharpDevelop.Gui return; } messageCategories.Add(category); - category.Cleared += new EventHandler(CategoryTextCleared); category.TextSet += new TextEventHandler(CategoryTextSet); category.TextAppended += new TextEventHandler(CategoryTextAppended); OnMessageCategoryAdded(EventArgs.Empty); } - void CategoryTextCleared(object sender, EventArgs e) - { - WorkbenchSingleton.SafeThreadAsyncCall(new Action(ClearText), - (MessageViewCategory)sender); - } - void ClearText(MessageViewCategory category) + void CategoryTextSet(object sender, TextEventArgs e) { - if (messageCategories[SelectedCategoryIndex] == category) { - textEditorControl.Text = String.Empty; - //textEditorControl.Refresh(); - } + EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, true)); } - void CategoryTextSet(object sender, TextEventArgs e) + struct AppendCall { - WorkbenchSingleton.SafeThreadAsyncCall(new Action(SetText), - (MessageViewCategory)sender, e.Text); + internal readonly MessageViewCategory Category; + internal readonly string Text; + internal readonly bool ClearCategory; + + public AppendCall(MessageViewCategory category, string text, bool clearCategory) + { + this.Category = category; + this.Text = text; + this.ClearCategory = clearCategory; + } } - object appendCallLock = new object(); - volatile int pendingAppendCalls = 0; + readonly object appendLock = new object(); + List appendCalls = new List(); void CategoryTextAppended(object sender, TextEventArgs e) { - lock (appendCallLock) { - pendingAppendCalls += 1; - MessageViewCategory cat = (MessageViewCategory)sender; - if (pendingAppendCalls < 5) { - WorkbenchSingleton.SafeThreadAsyncCall(new Action(AppendText), - cat, cat.Text, e.Text); - } else if (pendingAppendCalls == 5) { - WorkbenchSingleton.SafeThreadAsyncCall(new Action(AppendTextCombined), - cat); - } - } + EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, false)); } - void AppendTextCombined(MessageViewCategory category) + void EnqueueAppend(AppendCall appendCall) { - Application.DoEvents(); - Thread.Sleep(50); - Application.DoEvents(); - lock (appendCallLock) { - NativeMethods.SetWindowRedraw(textEditorControl.Handle, false); - SetText(category, category.Text); - NativeMethods.SetWindowRedraw(textEditorControl.Handle, true); - textEditorControl.SelectionStart = textEditorControl.TextLength; - if (LoggingService.IsDebugEnabled) { - LoggingService.Debug("Replaced " + pendingAppendCalls + " appends with one set call"); - } - pendingAppendCalls = 0; - } - textEditorControl.Refresh(); - } - - void AppendText(MessageViewCategory category, string fullText, string text) + bool waitForMainThread; + lock (appendLock) { + appendCalls.Add(appendCall); + if (appendCalls.Count == 1) + WorkbenchSingleton.SafeThreadAsyncCall(ProcessAppendText); + waitForMainThread = appendCalls.Count > 2000; + } + if (waitForMainThread && WorkbenchSingleton.InvokeRequired) { + int sleepLength = 20; + do { + Thread.Sleep(sleepLength); + sleepLength += 20; + lock (appendLock) + waitForMainThread = appendCalls.Count > 2000; + //if (waitForMainThread) LoggingService.Debug("Extending sleep (" + sleepLength + ")"); + } while (waitForMainThread); + } + } + + void ProcessAppendText() { - lock (appendCallLock) { - if (pendingAppendCalls >= 5) { - return; - } - pendingAppendCalls -= 1; + List appendCalls; + lock (appendLock) { + appendCalls = this.appendCalls; + this.appendCalls = new List(); } - if (messageCategories[SelectedCategoryIndex] != category) { - SelectCategory(category.Category, fullText); + Debug.Assert(appendCalls.Count > 0); + if (appendCalls.Count == 0) return; - } - if (text != null) { - text = StringParser.Parse(text); - textEditorControl.AppendText(text); - textEditorControl.SelectionStart = textEditorControl.TextLength; - /*textEditorControl.Document.ReadOnly = false; - textEditorControl.Document.Insert(textEditorControl.Document.TextLength, text); - textEditorControl.Document.ReadOnly = true; - textEditorControl.ActiveTextAreaControl.Caret.Position = new Point(0, textEditorControl.Document.TotalNumberOfLines); - textEditorControl.ActiveTextAreaControl.ScrollTo(textEditorControl.Document.TotalNumberOfLines);*/ - } - } - - void SetText(MessageViewCategory category, string text) - { - if (messageCategories[SelectedCategoryIndex] != category) { - SelectCategory(category.Category); + + MessageViewCategory newCategory = appendCalls[appendCalls.Count - 1].Category; + if (messageCategories[SelectedCategoryIndex] != newCategory) { + SelectCategory(newCategory.Category); return; } - if (text == null) { - text = String.Empty; + + bool clear; + string text; + if (appendCalls.Count == 1) { + //LoggingService.Debug("CompilerMessageView: Single append."); + clear = appendCalls[0].ClearCategory; + text = appendCalls[0].Text; + } else { + if (LoggingService.IsDebugEnabled) { + LoggingService.Debug("CompilerMessageView: Combined " + appendCalls.Count + " appends."); + } + + clear = false; + StringBuilder b = new StringBuilder(); + foreach (AppendCall append in appendCalls) { + if (append.Category == newCategory) { + if (append.ClearCategory) { + b.Length = 0; + clear = true; + } + b.Append(append.Text); + } + } + text = b.ToString(); + } + + //NativeMethods.SetWindowRedraw(textEditorControl.Handle, false); + if (clear) { + textEditorControl.Text = text; } else { - text = StringParser.Parse(text); + textEditorControl.SelectionStart = textEditorControl.TextLength; + textEditorControl.SelectedText = text; } - textEditorControl.Text = text; - //textEditorControl.Refresh(); + //NativeMethods.SetWindowRedraw(textEditorControl.Handle, true); + textEditorControl.SelectionStart = textEditorControl.TextLength; } public void SelectCategory(string categoryName) diff --git a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs index 01d7743e14..3412e822c9 100644 --- a/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs +++ b/src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs @@ -16,9 +16,46 @@ namespace ICSharpCode.SharpDevelop.Gui /// public class MessageViewCategory { + #region Static methods to create MessageViewCategories + /// + /// Creates a new MessageViewCategory with the specified category + /// and adds it to the CompilerMessageView pad. + /// This method is thread-safe and works correctly even if called multiple times for the same + /// thread; only one messageViewCategory will be created. + /// + public static void Create(ref MessageViewCategory messageViewCategory, string category) + { + Create(ref messageViewCategory, category, category); + } + + /// + /// Creates a new MessageViewCategory with the specified category + /// and adds it to the CompilerMessageView pad. + /// This method is thread-safe and works correctly even if called concurrently for the same + /// category; only one messageViewCategory will be created. + /// + public static void Create(ref MessageViewCategory messageViewCategory, string category, string displayCategory) + { + MessageViewCategory newMessageViewCategory = new MessageViewCategory(category, displayCategory); + if (System.Threading.Interlocked.CompareExchange(ref messageViewCategory, newMessageViewCategory, null) == null) { + // this thread was successful creating the category, so add it + CompilerMessageView.Instance.AddCategory(newMessageViewCategory); + } + } + #endregion + string category; string displayCategory; - StringBuilder textBuilder = new StringBuilder(); + readonly StringBuilder textBuilder = new StringBuilder(); + + /// + /// Gets the object on which the MessageViewCategory locks. + /// + public object SyncRoot { + get { + return textBuilder; + } + } public string Category { get { @@ -50,32 +87,6 @@ namespace ICSharpCode.SharpDevelop.Gui this.displayCategory = displayCategory; } - /// - /// Creates a new MessageViewCategory with the specified category - /// and adds it to the CompilerMessageView pad. - /// This method is thread-safe and works correctly even if called multiple times for the same - /// thread; only one messageViewCategory will be created. - /// - public static void Create(ref MessageViewCategory messageViewCategory, string category) - { - Create(ref messageViewCategory, category, category); - } - - /// - /// Creates a new MessageViewCategory with the specified category - /// and adds it to the CompilerMessageView pad. - /// This method is thread-safe and works correctly even if called concurrently for the same - /// category; only one messageViewCategory will be created. - /// - public static void Create(ref MessageViewCategory messageViewCategory, string category, string displayCategory) - { - MessageViewCategory newMessageViewCategory = new MessageViewCategory(category, displayCategory); - if (System.Threading.Interlocked.CompareExchange(ref messageViewCategory, newMessageViewCategory, null) == null) { - // this thread was successful creating the category, so add it - CompilerMessageView.Instance.AddCategory(newMessageViewCategory); - } - } - public void AppendLine(string text) { AppendText(text + Environment.NewLine); @@ -85,25 +96,25 @@ namespace ICSharpCode.SharpDevelop.Gui { lock (textBuilder) { textBuilder.Append(text); + OnTextAppended(new TextEventArgs(text)); } - OnTextAppended(new TextEventArgs(text)); } public void SetText(string text) { lock (textBuilder) { + // clear text: textBuilder.Length = 0; + // reset capacity: we must shrink the textBuilder at some point to reclaim memory + textBuilder.Capacity = text.Length + 16; textBuilder.Append(text); + OnTextSet(new TextEventArgs(text)); } - OnTextSet(new TextEventArgs(text)); } public void ClearText() { - lock (textBuilder) { - textBuilder.Length = 0; - } - OnCleared(EventArgs.Empty); + SetText(string.Empty); } protected virtual void OnTextAppended(TextEventArgs e) @@ -113,14 +124,6 @@ namespace ICSharpCode.SharpDevelop.Gui } } - - protected virtual void OnCleared(EventArgs e) - { - if (Cleared != null) { - Cleared(this, e); - } - } - protected virtual void OnTextSet(TextEventArgs e) { if (TextSet != null) { @@ -128,8 +131,21 @@ namespace ICSharpCode.SharpDevelop.Gui } } + /// + /// Is raised when text is appended to the MessageViewCategory. + /// Warning: This event is raised inside a lock held by the MessageViewCategory. This is necessary + /// to ensure TextAppended event handlers are called in the same order as text is appended to the category + /// when there are multiple threads writing to the same category. + /// public event TextEventHandler TextAppended; + + /// + /// Is raised when text is appended to the MessageViewCategory. + /// Warning: This event is raised inside a lock held by the MessageViewCategory. This is necessary + /// to ensure TextAppended and TextSet event handlers are called in the same order as + /// text is appended or set + /// when there are multiple threads writing to the same category. + /// public event TextEventHandler TextSet; - public event EventHandler Cleared; } } diff --git a/src/Main/Base/Project/Src/Project/BuildEngine.cs b/src/Main/Base/Project/Src/Project/BuildEngine.cs index 1300390cd3..c9e5359bb6 100644 --- a/src/Main/Base/Project/Src/Project/BuildEngine.cs +++ b/src/Main/Base/Project/Src/Project/BuildEngine.cs @@ -72,7 +72,7 @@ namespace ICSharpCode.SharpDevelop.Project public void ReportMessage(string message) { - messageView.AppendLine(message); + messageView.AppendLine(ICSharpCode.Core.StringParser.Parse(message)); } public void Done(bool success) diff --git a/src/Main/Base/Project/Src/Util/ProcessRunner.cs b/src/Main/Base/Project/Src/Util/ProcessRunner.cs index 7401e1717b..8e7447e818 100644 --- a/src/Main/Base/Project/Src/Util/ProcessRunner.cs +++ b/src/Main/Base/Project/Src/Util/ProcessRunner.cs @@ -7,11 +7,10 @@ using System; using System.Diagnostics; -using System.Runtime.InteropServices; using System.Text; +using System.Threading; using ICSharpCode.Core; -using System.Threading; namespace ICSharpCode.SharpDevelop.Util { @@ -47,6 +46,7 @@ namespace ICSharpCode.SharpDevelop.Util /// public ProcessRunner() { + this.LogStandardOutputAndError = true; } /// @@ -54,6 +54,14 @@ namespace ICSharpCode.SharpDevelop.Util /// public string WorkingDirectory { get; set; } + /// + /// Gets or sets whether standard output is logged to the "StandardOutput" and "StandardError" + /// properties. When this property is false, output is still redirected to the + /// OutputLineReceived and ErrorLineReceived events, but the ProcessRunner uses less memory. + /// The default value is true. + /// + public bool LogStandardOutputAndError { get; set; } + /// /// Gets the standard output returned from the process. /// @@ -161,19 +169,19 @@ namespace ICSharpCode.SharpDevelop.Util process.Exited += OnProcessExited; } - bool started = false; - try { - process.Start(); - started = true; - } finally { - if (!started) { - process.Exited -= OnProcessExited; - process = null; - } + bool started = false; + try { + process.Start(); + started = true; + } finally { + if (!started) { + process.Exited -= OnProcessExited; + process = null; } - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); + } + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); } /// @@ -229,8 +237,10 @@ namespace ICSharpCode.SharpDevelop.Util endOfOutput.Set(); return; } - lock (standardOutput) { - standardOutput.AppendLine(e.Data); + if (LogStandardOutputAndError) { + lock (standardOutput) { + standardOutput.AppendLine(e.Data); + } } if (OutputLineReceived != null) { OutputLineReceived(this, new LineReceivedEventArgs(e.Data)); @@ -249,8 +259,10 @@ namespace ICSharpCode.SharpDevelop.Util endOfOutput.Set(); return; } - lock (standardError) { - standardError.AppendLine(e.Data); + if (LogStandardOutputAndError) { + lock (standardError) { + standardError.AppendLine(e.Data); + } } if (ErrorLineReceived != null) { ErrorLineReceived(this, new LineReceivedEventArgs(e.Data));