Browse Source

Reduce memory consumption of CompilerMessageView. Related to SD2-1450 (Large amounts of output cause OutOfMemoryException).

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/branches/3.0@3516 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
ea1c893a4a
  1. 1
      src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs
  2. 3
      src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs
  3. 172
      src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs
  4. 100
      src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs
  5. 2
      src/Main/Base/Project/Src/Project/BuildEngine.cs
  6. 16
      src/Main/Base/Project/Src/Util/ProcessRunner.cs

1
src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs

@ -430,6 +430,7 @@ namespace ICSharpCode.UnitTesting
public RunTestInPadCommand() public RunTestInPadCommand()
{ {
runner = new ProcessRunner(); runner = new ProcessRunner();
runner.LogStandardOutputAndError = false;
runner.OutputLineReceived += OutputLineReceived; runner.OutputLineReceived += OutputLineReceived;
runner.ProcessExited += ProcessExited; runner.ProcessExited += ProcessExited;
} }

3
src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs

@ -256,6 +256,7 @@ namespace ICSharpCode.SharpDevelop.Commands
try { try {
if (tool.UseOutputPad) { if (tool.UseOutputPad) {
ProcessRunner processRunner = new ProcessRunner(); ProcessRunner processRunner = new ProcessRunner();
processRunner.LogStandardOutputAndError = false;
processRunner.ProcessExited += ProcessExitEvent; processRunner.ProcessExited += ProcessExitEvent;
processRunner.OutputLineReceived += process_OutputLineReceived; processRunner.OutputLineReceived += process_OutputLineReceived;
processRunner.ErrorLineReceived += process_OutputLineReceived; processRunner.ErrorLineReceived += process_OutputLineReceived;
@ -289,7 +290,7 @@ namespace ICSharpCode.SharpDevelop.Commands
WorkbenchSingleton.SafeThreadAsyncCall( WorkbenchSingleton.SafeThreadAsyncCall(
delegate { delegate {
ProcessRunner p = (ProcessRunner)sender; 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(); p.Dispose();
}); });
} }

172
src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/CompilerMessageView.cs

@ -7,7 +7,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Text;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
@ -56,15 +58,29 @@ namespace ICSharpCode.SharpDevelop.Gui
return selectedCategory; return selectedCategory;
} }
set { set {
WorkbenchSingleton.AssertMainThread();
if (selectedCategory != value) { if (selectedCategory != value) {
selectedCategory = value; selectedCategory = value;
textEditorControl.Text = (value < 0) ? "" : StringParser.Parse(messageCategories[value].Text); DisplayActiveCategory();
//textEditorControl.Refresh();
OnSelectedCategoryIndexChanged(EventArgs.Empty); 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 { public bool WordWrap {
get { get {
return properties.Get("WordWrap", true); return properties.Get("WordWrap", true);
@ -136,19 +152,17 @@ namespace ICSharpCode.SharpDevelop.Gui
SetWordWrap(); SetWordWrap();
myPanel.ResumeLayout(false); myPanel.ResumeLayout(false);
SetText(messageCategories[selectedCategory], messageCategories[selectedCategory].Text); DisplayActiveCategory();
ProjectService.SolutionLoaded += SolutionLoaded; ProjectService.SolutionLoaded += SolutionLoaded;
} }
void SolutionLoaded(object sender, SolutionEventArgs e) void SolutionLoaded(object sender, SolutionEventArgs e)
{ {
foreach (MessageViewCategory category in messageCategories) foreach (MessageViewCategory category in messageCategories) {
{ category.ClearText();
ClearText(category);
} }
} }
void SetWordWrap() void SetWordWrap()
{ {
bool wordWrap = this.WordWrap; bool wordWrap = this.WordWrap;
@ -179,105 +193,111 @@ namespace ICSharpCode.SharpDevelop.Gui
return; return;
} }
messageCategories.Add(category); messageCategories.Add(category);
category.Cleared += new EventHandler(CategoryTextCleared);
category.TextSet += new TextEventHandler(CategoryTextSet); category.TextSet += new TextEventHandler(CategoryTextSet);
category.TextAppended += new TextEventHandler(CategoryTextAppended); category.TextAppended += new TextEventHandler(CategoryTextAppended);
OnMessageCategoryAdded(EventArgs.Empty); OnMessageCategoryAdded(EventArgs.Empty);
} }
void CategoryTextCleared(object sender, EventArgs e) void CategoryTextSet(object sender, TextEventArgs e)
{ {
WorkbenchSingleton.SafeThreadAsyncCall(new Action<MessageViewCategory>(ClearText), EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, true));
(MessageViewCategory)sender);
} }
void ClearText(MessageViewCategory category)
struct AppendCall
{ {
if (messageCategories[SelectedCategoryIndex] == category) { internal readonly MessageViewCategory Category;
textEditorControl.Text = String.Empty; internal readonly string Text;
//textEditorControl.Refresh(); internal readonly bool ClearCategory;
}
}
void CategoryTextSet(object sender, TextEventArgs e) public AppendCall(MessageViewCategory category, string text, bool clearCategory)
{ {
WorkbenchSingleton.SafeThreadAsyncCall(new Action<MessageViewCategory, string>(SetText), this.Category = category;
(MessageViewCategory)sender, e.Text); this.Text = text;
this.ClearCategory = clearCategory;
}
} }
object appendCallLock = new object(); readonly object appendLock = new object();
volatile int pendingAppendCalls = 0; List<AppendCall> appendCalls = new List<AppendCall>();
void CategoryTextAppended(object sender, TextEventArgs e) void CategoryTextAppended(object sender, TextEventArgs e)
{ {
lock (appendCallLock) { EnqueueAppend(new AppendCall((MessageViewCategory)sender, e.Text, false));
pendingAppendCalls += 1;
MessageViewCategory cat = (MessageViewCategory)sender;
if (pendingAppendCalls < 5) {
WorkbenchSingleton.SafeThreadAsyncCall(new Action<MessageViewCategory, string, string>(AppendText),
cat, cat.Text, e.Text);
} else if (pendingAppendCalls == 5) {
WorkbenchSingleton.SafeThreadAsyncCall(new Action<MessageViewCategory>(AppendTextCombined),
cat);
}
}
} }
void AppendTextCombined(MessageViewCategory category) void EnqueueAppend(AppendCall appendCall)
{ {
Application.DoEvents(); bool waitForMainThread;
Thread.Sleep(50); lock (appendLock) {
Application.DoEvents(); appendCalls.Add(appendCall);
lock (appendCallLock) { if (appendCalls.Count == 1)
NativeMethods.SetWindowRedraw(textEditorControl.Handle, false); WorkbenchSingleton.SafeThreadAsyncCall(ProcessAppendText);
SetText(category, category.Text); waitForMainThread = appendCalls.Count > 2000;
NativeMethods.SetWindowRedraw(textEditorControl.Handle, true); }
textEditorControl.SelectionStart = textEditorControl.TextLength; if (waitForMainThread && WorkbenchSingleton.InvokeRequired) {
if (LoggingService.IsDebugEnabled) { int sleepLength = 20;
LoggingService.Debug("Replaced " + pendingAppendCalls + " appends with one set call"); do {
} Thread.Sleep(sleepLength);
pendingAppendCalls = 0; sleepLength += 20;
} lock (appendLock)
textEditorControl.Refresh(); waitForMainThread = appendCalls.Count > 2000;
//if (waitForMainThread) LoggingService.Debug("Extending sleep (" + sleepLength + ")");
} while (waitForMainThread);
}
}
void ProcessAppendText()
{
List<AppendCall> appendCalls;
lock (appendLock) {
appendCalls = this.appendCalls;
this.appendCalls = new List<AppendCall>();
} }
Debug.Assert(appendCalls.Count > 0);
if (appendCalls.Count == 0)
return;
void AppendText(MessageViewCategory category, string fullText, string text) MessageViewCategory newCategory = appendCalls[appendCalls.Count - 1].Category;
{ if (messageCategories[SelectedCategoryIndex] != newCategory) {
lock (appendCallLock) { SelectCategory(newCategory.Category);
if (pendingAppendCalls >= 5) {
return; return;
} }
pendingAppendCalls -= 1;
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.");
} }
if (messageCategories[SelectedCategoryIndex] != category) {
SelectCategory(category.Category, fullText); clear = false;
return; StringBuilder b = new StringBuilder();
foreach (AppendCall append in appendCalls) {
if (append.Category == newCategory) {
if (append.ClearCategory) {
b.Length = 0;
clear = true;
} }
if (text != null) { b.Append(append.Text);
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);*/
} }
} }
text = b.ToString();
void SetText(MessageViewCategory category, string text)
{
if (messageCategories[SelectedCategoryIndex] != category) {
SelectCategory(category.Category);
return;
} }
if (text == null) {
text = String.Empty; //NativeMethods.SetWindowRedraw(textEditorControl.Handle, false);
if (clear) {
textEditorControl.Text = text;
} else { } else {
text = StringParser.Parse(text); textEditorControl.SelectionStart = textEditorControl.TextLength;
textEditorControl.SelectedText = text;
} }
textEditorControl.Text = text; //NativeMethods.SetWindowRedraw(textEditorControl.Handle, true);
//textEditorControl.Refresh(); textEditorControl.SelectionStart = textEditorControl.TextLength;
} }
public void SelectCategory(string categoryName) public void SelectCategory(string categoryName)

100
src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategory.cs

@ -16,9 +16,46 @@ namespace ICSharpCode.SharpDevelop.Gui
/// </summary> /// </summary>
public class MessageViewCategory public class MessageViewCategory
{ {
#region Static methods to create MessageViewCategories
/// <summary>
/// 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.
/// </summary>
public static void Create(ref MessageViewCategory messageViewCategory, string category)
{
Create(ref messageViewCategory, category, category);
}
/// <summary>
/// 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.
/// </summary>
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 category;
string displayCategory; string displayCategory;
StringBuilder textBuilder = new StringBuilder(); readonly StringBuilder textBuilder = new StringBuilder();
/// <summary>
/// Gets the object on which the MessageViewCategory locks.
/// </summary>
public object SyncRoot {
get {
return textBuilder;
}
}
public string Category { public string Category {
get { get {
@ -50,32 +87,6 @@ namespace ICSharpCode.SharpDevelop.Gui
this.displayCategory = displayCategory; this.displayCategory = displayCategory;
} }
/// <summary>
/// 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.
/// </summary>
public static void Create(ref MessageViewCategory messageViewCategory, string category)
{
Create(ref messageViewCategory, category, category);
}
/// <summary>
/// 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.
/// </summary>
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) public void AppendLine(string text)
{ {
AppendText(text + Environment.NewLine); AppendText(text + Environment.NewLine);
@ -85,25 +96,25 @@ namespace ICSharpCode.SharpDevelop.Gui
{ {
lock (textBuilder) { lock (textBuilder) {
textBuilder.Append(text); textBuilder.Append(text);
}
OnTextAppended(new TextEventArgs(text)); OnTextAppended(new TextEventArgs(text));
} }
}
public void SetText(string text) public void SetText(string text)
{ {
lock (textBuilder) { lock (textBuilder) {
// clear text:
textBuilder.Length = 0; textBuilder.Length = 0;
// reset capacity: we must shrink the textBuilder at some point to reclaim memory
textBuilder.Capacity = text.Length + 16;
textBuilder.Append(text); textBuilder.Append(text);
}
OnTextSet(new TextEventArgs(text)); OnTextSet(new TextEventArgs(text));
} }
}
public void ClearText() public void ClearText()
{ {
lock (textBuilder) { SetText(string.Empty);
textBuilder.Length = 0;
}
OnCleared(EventArgs.Empty);
} }
protected virtual void OnTextAppended(TextEventArgs e) 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) protected virtual void OnTextSet(TextEventArgs e)
{ {
if (TextSet != null) { if (TextSet != null) {
@ -128,8 +131,21 @@ namespace ICSharpCode.SharpDevelop.Gui
} }
} }
/// <summary>
/// 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.
/// </summary>
public event TextEventHandler TextAppended; public event TextEventHandler TextAppended;
/// <summary>
/// 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.
/// </summary>
public event TextEventHandler TextSet; public event TextEventHandler TextSet;
public event EventHandler Cleared;
} }
} }

2
src/Main/Base/Project/Src/Project/BuildEngine.cs

@ -72,7 +72,7 @@ namespace ICSharpCode.SharpDevelop.Project
public void ReportMessage(string message) public void ReportMessage(string message)
{ {
messageView.AppendLine(message); messageView.AppendLine(ICSharpCode.Core.StringParser.Parse(message));
} }
public void Done(bool success) public void Done(bool success)

16
src/Main/Base/Project/Src/Util/ProcessRunner.cs

@ -7,11 +7,10 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using ICSharpCode.Core; using ICSharpCode.Core;
using System.Threading;
namespace ICSharpCode.SharpDevelop.Util namespace ICSharpCode.SharpDevelop.Util
{ {
@ -47,6 +46,7 @@ namespace ICSharpCode.SharpDevelop.Util
/// </summary> /// </summary>
public ProcessRunner() public ProcessRunner()
{ {
this.LogStandardOutputAndError = true;
} }
/// <summary> /// <summary>
@ -54,6 +54,14 @@ namespace ICSharpCode.SharpDevelop.Util
/// </summary> /// </summary>
public string WorkingDirectory { get; set; } public string WorkingDirectory { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool LogStandardOutputAndError { get; set; }
/// <summary> /// <summary>
/// Gets the standard output returned from the process. /// Gets the standard output returned from the process.
/// </summary> /// </summary>
@ -229,9 +237,11 @@ namespace ICSharpCode.SharpDevelop.Util
endOfOutput.Set(); endOfOutput.Set();
return; return;
} }
if (LogStandardOutputAndError) {
lock (standardOutput) { lock (standardOutput) {
standardOutput.AppendLine(e.Data); standardOutput.AppendLine(e.Data);
} }
}
if (OutputLineReceived != null) { if (OutputLineReceived != null) {
OutputLineReceived(this, new LineReceivedEventArgs(e.Data)); OutputLineReceived(this, new LineReceivedEventArgs(e.Data));
} }
@ -249,9 +259,11 @@ namespace ICSharpCode.SharpDevelop.Util
endOfOutput.Set(); endOfOutput.Set();
return; return;
} }
if (LogStandardOutputAndError) {
lock (standardError) { lock (standardError) {
standardError.AppendLine(e.Data); standardError.AppendLine(e.Data);
} }
}
if (ErrorLineReceived != null) { if (ErrorLineReceived != null) {
ErrorLineReceived(this, new LineReceivedEventArgs(e.Data)); ErrorLineReceived(this, new LineReceivedEventArgs(e.Data));
} }

Loading…
Cancel
Save