diff --git a/src/Lucene.Net/Analysis/Analyzer.cs b/src/Lucene.Net/Analysis/Analyzer.cs
index eb1d1b6e80..8477d74d6d 100644
--- a/src/Lucene.Net/Analysis/Analyzer.cs
+++ b/src/Lucene.Net/Analysis/Analyzer.cs
@@ -1,4 +1,5 @@
-using Lucene.Net.Util;
+using Lucene.Net.Index;
+using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.IO;
@@ -314,7 +315,7 @@ public TokenStream GetTokenStream(string fieldName, string text)
/// The default implementation returns
/// unchanged.
///
- /// name being indexed
+ /// name being indexed
/// original
/// reader, optionally decorated with (s)
protected internal virtual TextReader InitReader(string fieldName, TextReader reader)
@@ -323,16 +324,16 @@ protected internal virtual TextReader InitReader(string fieldName, TextReader re
}
///
- /// Invoked before indexing a instance if
+ /// Invoked before indexing a instance if
/// terms have already been added to that field. This allows custom
/// analyzers to place an automatic position increment gap between
- /// instances using the same field name. The default value
+ /// instances using the same field name. The default value
/// position increment gap is 0. With a 0 position increment gap and
/// the typical default token position increment of 1, all terms in a field,
- /// including across instances, are in successive positions, allowing
- /// exact matches, for instance, across instance boundaries.
+ /// including across instances, are in successive positions, allowing
+ /// exact matches, for instance, across instance boundaries.
///
- /// name being indexed.
+ /// name being indexed.
/// position increment gap, added to the next token emitted from .
/// this value must be >= 0.
public virtual int GetPositionIncrementGap(string fieldName)
@@ -459,11 +460,28 @@ public override void SetReusableComponents(Analyzer analyzer, string fieldName,
if (componentsPerField is null)
{
// LUCENENET-615: This needs to support nullable keys
- componentsPerField = new JCG.Dictionary();
+ componentsPerField = new TokenStreamComponentsDictionary();
SetStoredValue(analyzer, componentsPerField);
}
componentsPerField[fieldName] = components;
}
+
+ ///
+ /// A dictionary that supports disposing of the values when the dictionary is disposed.
+ ///
+ ///
+ private class TokenStreamComponentsDictionary
+ : JCG.Dictionary, IDisposable
+ {
+ public void Dispose()
+ {
+ foreach (var kvp in this)
+ {
+ kvp.Value?.Dispose();
+ }
+ Clear();
+ }
+ }
}
///
@@ -508,7 +526,17 @@ protected internal override TextReader InitReader(string fieldName, TextReader r
/// returned by
/// .
///
- public class TokenStreamComponents
+ ///
+ /// LUCENENET: This class implements IDisposable so that any TokenStream implementations
+ /// that need to be disposed are disposed when the Analyzer that stores this in its
+ /// stored value is disposed.
+ ///
+ /// Because it's impossible to know if the would dispose of the ,
+ /// this class calls on both if they are not reference equal.
+ /// Implementations of should be careful to make their
+ /// code idempotent so that calling multiple times has no effect.
+ ///
+ public class TokenStreamComponents : IDisposable
{
///
/// Original source of the tokens.
@@ -573,6 +601,25 @@ protected internal virtual void SetReader(TextReader reader)
///
/// Component's
public virtual Tokenizer Tokenizer => m_source;
+
+ ///
+ /// Disposes of the and .
+ ///
+ ///
+ /// LUCENENET specific: see remarks on the class.
+ ///
+ public void Dispose()
+ {
+ m_source?.Dispose();
+
+ if (!ReferenceEquals(m_source, m_sink))
+ {
+ m_sink?.Dispose();
+ }
+
+ reusableStringReader?.Dispose();
+ GC.SuppressFinalize(this);
+ }
}
///
diff --git a/src/Lucene.Net/Analysis/TokenStream.cs b/src/Lucene.Net/Analysis/TokenStream.cs
index d0ba1e7f3b..07f891aa60 100644
--- a/src/Lucene.Net/Analysis/TokenStream.cs
+++ b/src/Lucene.Net/Analysis/TokenStream.cs
@@ -1,4 +1,5 @@
using Lucene.Net.Analysis.TokenAttributes;
+using Lucene.Net.Index;
using Lucene.Net.Util;
using System;
using System.IO;
@@ -77,7 +78,7 @@ namespace Lucene.Net.Analysis
/// Therefore all non-abstract subclasses must be sealed or have at least a sealed
/// implementation of ! This is checked when assertions are enabled.
///
- public abstract class TokenStream : AttributeSource, ICloseable
+ public abstract class TokenStream : AttributeSource, ICloseable, IDisposable
{
///
/// A using the default attribute factory.
@@ -110,7 +111,7 @@ protected TokenStream(AttributeFactory factory)
}
///
- /// Consumers (i.e., ) use this method to advance the stream to
+ /// Consumers (i.e., ) use this method to advance the stream to
/// the next token. Implementing classes must implement this method and update
/// the appropriate s with the attributes of the next
/// token.
@@ -187,10 +188,28 @@ public virtual void Reset()
///
///
/// LUCENENET notes - this is intended to release resources in a way that allows the
- /// object to be reused, so it is not the same as .
+ /// object to be reused, so it is not the same as .
///
public virtual void Close()
{
}
+
+ // LUCENENET specific - implementing proper dispose pattern
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases resources associated with this stream, in a way such that the stream is not reusable.
+ ///
+ /// If you override this method, always call base.Dispose(disposing).
+ /// Also, ensure that your implementation is idempotent as it may be called multiple times.
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ }
}
}
diff --git a/src/Lucene.Net/Util/CloseableThreadLocal.cs b/src/Lucene.Net/Util/CloseableThreadLocal.cs
index c36b51ac11..004e019958 100644
--- a/src/Lucene.Net/Util/CloseableThreadLocal.cs
+++ b/src/Lucene.Net/Util/CloseableThreadLocal.cs
@@ -39,7 +39,7 @@ namespace Lucene.Net.Util
///
/// This class works around the issue by using an alternative approach than using .
/// It keeps track of each thread's local and global state in order to later optimize garbage collection.
- /// A complete explanation can be found at
+ /// A complete explanation can be found at
///
/// https://ayende.com/blog/189793-A/the-design-and-implementation-of-a-better-threadlocal-t.
///
@@ -169,6 +169,21 @@ public void Dispose()
if (copy is null)
return;
+ foreach (var value in copy.Values)
+ {
+ if (value is IDisposable disposable)
+ {
+ try
+ {
+ disposable.Dispose();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ }
+
Interlocked.Increment(ref globalVersion);
_disposed = true;
_values = null;
@@ -298,4 +313,4 @@ private sealed class LocalState
public int localVersion;
}
}
-}
\ No newline at end of file
+}