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 +}