Browse Source

Fix premature disposal of IProgressMonitor in parallel background search.

This was the cause of "InvalidOperationException: The LinkedList node does not belong to current LinkedList." in ProgressCollector.UnregisterNamedMonitor().
pull/297/head
Daniel Grunwald 12 years ago
parent
commit
c19a83f1d1
  1. 19
      src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs
  2. 10
      src/Main/Base/Project/Editor/Search/ISearchResultFactory.cs
  3. 18
      src/Main/Base/Project/Editor/Search/SearchResultsPad.cs
  4. 2
      src/Main/Base/Project/Util/IProgressMonitor.cs
  5. 3
      src/Main/Base/Project/Util/ProgressCollector.cs

19
src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs

@ -28,6 +28,14 @@ namespace SearchAndReplace @@ -28,6 +28,14 @@ namespace SearchAndReplace
public class SearchManager
{
#region FindAll
/// <summary>
/// Prepares a parallel search operation.
/// </summary>
/// <param name="strategy">The search strategy. Contains the search term and options.</param>
/// <param name="location">The location to search in.</param>
/// <param name="progressMonitor">The progress monitor used to report progress.
/// The parallel search operation will take ownership of this monitor: the monitor is disposed when the search finishes!</param>
/// <returns>Observable that starts performing the search when subscribed to. Does not support more than one subscriber!</returns>
public static IObservable<SearchedFile> FindAllParallel(ISearchStrategy strategy, SearchLocation location, IProgressMonitor progressMonitor)
{
currentSearchRegion = null;
@ -122,7 +130,7 @@ namespace SearchAndReplace @@ -122,7 +130,7 @@ namespace SearchAndReplace
observer.OnError(t.Exception);
else
observer.OnCompleted();
this.Dispose();
monitor.Dispose();
});
return this;
}
@ -181,8 +189,9 @@ namespace SearchAndReplace @@ -181,8 +189,9 @@ namespace SearchAndReplace
public void Dispose()
{
// Warning: Dispose() may be called concurrently while the operation is still in progress.
// We cannot dispose the progress monitor here because it is still in use by the search operation.
cts.Cancel();
monitor.Dispose();
}
SearchedFile SearchFile(FileName fileName)
@ -509,6 +518,12 @@ namespace SearchAndReplace @@ -509,6 +518,12 @@ namespace SearchAndReplace
&& result.Length == editor.SelectionLength;
}
/// <summary>
/// Shows searchs results in the search results pad, and brings that pad to front.
/// </summary>
/// <param name="pattern">The pattern that is being searched for; used for the title of the search in the list of past searched.</param>
/// <param name="results">An observable that represents a background search operation.
/// The search results pad will subscribe to this observable in order to receive the search results.</param>
public static void ShowSearchResults(string pattern, IObservable<SearchedFile> results)
{
string title = StringParser.Parse("${res:MainWindow.Windows.SearchResultPanel.OccurrencesOf}",

10
src/Main/Base/Project/Editor/Search/ISearchResultFactory.cs

@ -12,8 +12,18 @@ namespace ICSharpCode.SharpDevelop.Editor.Search @@ -12,8 +12,18 @@ namespace ICSharpCode.SharpDevelop.Editor.Search
/// </summary>
public interface ISearchResultFactory
{
/// <summary>
/// Creates an <see cref="ISearchResult"/> object from a list of matches.
/// </summary>
/// <param name="title">The title of the search.</param>
/// <param name="matches">The list of matches. CreateSearchResult() will enumerate once through the IEnumerable in order to retrieve the search results.</param>
ISearchResult CreateSearchResult(string title, IEnumerable<SearchResultMatch> matches);
/// <summary>
/// Creates an <see cref="ISearchResult"/> object for a background search operation.
/// </summary>
/// <param name="title">The title of the search.</param>
/// <param name="matches">The background search operation. CreateSearchResult() will subscribe to the observable in order to retrieve the search results.</param>
ISearchResult CreateSearchResult(string title, IObservable<SearchedFile> matches);
}
}

18
src/Main/Base/Project/Editor/Search/SearchResultsPad.cs

@ -78,6 +78,10 @@ namespace ICSharpCode.SharpDevelop.Editor.Search @@ -78,6 +78,10 @@ namespace ICSharpCode.SharpDevelop.Editor.Search
SD.WinForms.SetContent(contentPlaceholder, null);
}
/// <summary>
/// Shows a search in the search results pad.
/// The previously shown search will be stored in the list of past searches.
/// </summary>
public void ShowSearchResults(ISearchResult result)
{
if (result == null)
@ -108,11 +112,23 @@ namespace ICSharpCode.SharpDevelop.Editor.Search @@ -108,11 +112,23 @@ namespace ICSharpCode.SharpDevelop.Editor.Search
SearchResultsShown(this, EventArgs.Empty);
}
/// <summary>
/// Shows a search in the search results pad.
/// The previously shown search will be stored in the list of past searches.
/// </summary>
/// <param name="title">The title of the search.</param>
/// <param name="matches">The list of matches. ShowSearchResults() will enumerate once through the IEnumerable in order to retrieve the search results.</param>
public void ShowSearchResults(string title, IEnumerable<SearchResultMatch> matches)
{
ShowSearchResults(CreateSearchResult(title, matches));
}
/// <summary>
/// Performs a background search in the search results pad.
/// The previously shown search will be stored in the list of past searches.
/// </summary>
/// <param name="title">The title of the search.</param>
/// <param name="matches">The background search operation. ShowSearchResults() will subscribe to the observable in order to retrieve the search results.</param>
public void ShowSearchResults(string title, IObservable<SearchedFile> matches)
{
ShowSearchResults(CreateSearchResult(title, matches));
@ -120,6 +136,7 @@ namespace ICSharpCode.SharpDevelop.Editor.Search @@ -120,6 +136,7 @@ namespace ICSharpCode.SharpDevelop.Editor.Search
public event EventHandler SearchResultsShown = delegate {};
/// <inheritdoc cref="ISearchResultFactory.CreateSearchResult(string,IEnumerable{SearchResultMatch})"/>
public static ISearchResult CreateSearchResult(string title, IEnumerable<SearchResultMatch> matches)
{
if (title == null)
@ -135,6 +152,7 @@ namespace ICSharpCode.SharpDevelop.Editor.Search @@ -135,6 +152,7 @@ namespace ICSharpCode.SharpDevelop.Editor.Search
}
/// <inheritdoc cref="ISearchResultFactory.CreateSearchResult(string,IObservable{SearchResultMatch})"/>
public static ISearchResult CreateSearchResult(string title, IObservable<SearchedFile> matches)
{
if (title == null)

2
src/Main/Base/Project/Util/IProgressMonitor.cs

@ -39,7 +39,7 @@ namespace ICSharpCode.SharpDevelop @@ -39,7 +39,7 @@ namespace ICSharpCode.SharpDevelop
/// </summary>
/// <param name="workAmount">The amount of work this sub-task performs in relation to the work of this task.
/// That means, this parameter is used as a scaling factor for work performed within the subtask.</param>
/// <param name="childCancellationToken">
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the sub-task.
/// Note: cancelling the main task will not cancel the sub-task.
/// </param>

3
src/Main/Base/Project/Util/ProgressCollector.cs

@ -191,6 +191,9 @@ namespace ICSharpCode.SharpDevelop @@ -191,6 +191,9 @@ namespace ICSharpCode.SharpDevelop
{
lock (namedMonitors) {
bool wasFirst = namedMonitors.First == nameEntry;
// Note: if Remove() crashes with "InvalidOperationException: The LinkedList node does not belong to current LinkedList.",
// that's an indication that the progress monitor is being disposed multiple times concurrently.
// (which is not allowed according to IProgressMonitor thread-safety documentation)
namedMonitors.Remove(nameEntry);
if (wasFirst)
SetTaskName(namedMonitors.First != null ? namedMonitors.First.Value : null);

Loading…
Cancel
Save