You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2023/05/16 21:03:06 UTC

[lucenenet] branch master updated (f1446f4c3 -> e9c47b2de)

This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git


    from f1446f4c3 Lucene.Net.Documents.DateTools: Added exceptions to documentation and added nullable reference type support.
     new 9d9f1dbf6 BUG: Lucene.Net.Store.NRTCachingDirectory: Don't throw exceptions when Dispose() is called multiple times. Also added checks to ensure an ObjectDisposedException is thrown by any method call after Dispose() is called. Fixed all other directory implementations and made the check atomic so only the first call to Dispose() is run. Fixes #841. Related to #265.
     new c49fd9a26 Lucene.Net.Store (Directory + MMapDirectory + NIOFSDirectory + SimpleFSDirectory): Upgraded all FSDirectories to allow multiple Dispose() calls on IndexInputSlicer and BufferedIndexInput subclasses. See #265.
     new 407fe1669 Lucene.Net.Codecs.MultiLevelSkipListReader: Allow double-dispose calls and guard against usage after Dispose(). See #265.
     new 3979c4ab3 Lucene.Net.Store.BufferedIndexOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.
     new a9c8388aa Lucene.Net.Store.RateLimitedIndexOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.
     new 5336dfc16 Lucene.Net.Store.InputStreamDataInput: Allow double-dispose calls and guard against usage after Dispose(). See #265.
     new e9c47b2de Lucene.Net.Store.OutputStreamDataOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../Store/BaseDirectoryTestCase.cs                 | 13 +++++
 .../Store/BaseDirectoryWrapper.cs                  | 59 ++++++++++++++++++++--
 .../Store/MockDirectoryWrapper.cs                  | 27 ++++++----
 .../Support/TestApiConsistency.cs                  |  2 +-
 src/Lucene.Net.Tests/Store/TestDirectory.cs        | 14 +++++
 .../Store/TestNRTCachingDirectory.cs               | 13 +++++
 src/Lucene.Net.Tests/Support/TestApiConsistency.cs |  2 +-
 src/Lucene.Net/Codecs/MultiLevelSkipListReader.cs  | 21 +++++++-
 src/Lucene.Net/Store/BaseDirectory.cs              | 55 +++++++++++++++++---
 src/Lucene.Net/Store/BufferedIndexOutput.cs        | 18 +++++++
 src/Lucene.Net/Store/CompoundFileDirectory.cs      | 11 ++--
 src/Lucene.Net/Store/Directory.cs                  |  7 +++
 src/Lucene.Net/Store/FSDirectory.cs                |  5 +-
 src/Lucene.Net/Store/InputStreamDataInput.cs       |  4 ++
 src/Lucene.Net/Store/MMapDirectory.cs              |  8 ++-
 src/Lucene.Net/Store/NIOFSDirectory.cs             |  8 +++
 src/Lucene.Net/Store/NRTCachingDirectory.cs        | 29 +++++++++--
 src/Lucene.Net/Store/OutputStreamDataOutput.cs     |  4 ++
 src/Lucene.Net/Store/RAMDirectory.cs               |  4 +-
 src/Lucene.Net/Store/RateLimitedIndexOutput.cs     | 26 +++++++++-
 src/Lucene.Net/Store/SimpleFSDirectory.cs          |  8 +++
 21 files changed, 296 insertions(+), 42 deletions(-)


[lucenenet] 03/07: Lucene.Net.Codecs.MultiLevelSkipListReader: Allow double-dispose calls and guard against usage after Dispose(). See #265.

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 407fe16692647cf1bd9af1c37e59f502c6c9dca8
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun May 14 22:56:32 2023 +0700

    Lucene.Net.Codecs.MultiLevelSkipListReader: Allow double-dispose calls and guard against usage after Dispose(). See #265.
---
 src/Lucene.Net/Codecs/MultiLevelSkipListReader.cs | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/Lucene.Net/Codecs/MultiLevelSkipListReader.cs b/src/Lucene.Net/Codecs/MultiLevelSkipListReader.cs
index 22856cf7d..53ad6f2e7 100644
--- a/src/Lucene.Net/Codecs/MultiLevelSkipListReader.cs
+++ b/src/Lucene.Net/Codecs/MultiLevelSkipListReader.cs
@@ -1,6 +1,7 @@
 using Lucene.Net.Diagnostics;
 using Lucene.Net.Support;
 using System;
+using System.Data.Common;
 using System.Runtime.CompilerServices;
 
 namespace Lucene.Net.Codecs
@@ -361,17 +362,26 @@ namespace Lucene.Net.Codecs
 
             public override long Position => pointer + pos; // LUCENENET specific: Renamed from getFilePointer() to match FileStream
 
-            public override long Length => data.Length;
+            public override long Length
+            {
+                get
+                {
+                    EnsureOpen(); // LUCENENET: Guard against disposed IndexInput
+                    return data.Length;
+                }
+            }
 
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
             public override byte ReadByte()
             {
+                EnsureOpen(); // LUCENENET: Guard against disposed IndexInput
                 return data[pos++];
             }
 
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
             public override void ReadBytes(byte[] b, int offset, int len)
             {
+                EnsureOpen(); // LUCENENET: Guard against disposed IndexInput
                 Arrays.Copy(data, pos, b, offset, len);
                 pos += len;
             }
@@ -379,8 +389,17 @@ namespace Lucene.Net.Codecs
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
             public override void Seek(long pos)
             {
+                EnsureOpen(); // LUCENENET: Guard against disposed IndexInput
                 this.pos = (int)(pos - pointer);
             }
+
+            private void EnsureOpen() // LUCENENET: Guard against disposed IndexInput
+            {
+                if (data is null)
+                {
+                    throw AlreadyClosedException.Create(this.GetType().FullName, "this IndexInput is disposed.");
+                }
+            }
         }
     }
 }
\ No newline at end of file


[lucenenet] 04/07: Lucene.Net.Store.BufferedIndexOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 3979c4ab3b05882595d796dc0fb42bcdd1409cf2
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun May 14 23:01:43 2023 +0700

    Lucene.Net.Store.BufferedIndexOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.
---
 src/Lucene.Net/Store/BufferedIndexOutput.cs | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/src/Lucene.Net/Store/BufferedIndexOutput.cs b/src/Lucene.Net/Store/BufferedIndexOutput.cs
index 6867c0a40..6be6f077f 100644
--- a/src/Lucene.Net/Store/BufferedIndexOutput.cs
+++ b/src/Lucene.Net/Store/BufferedIndexOutput.cs
@@ -1,6 +1,7 @@
 using Lucene.Net.Support;
 using System;
 using System.Runtime.CompilerServices;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -34,6 +35,7 @@ namespace Lucene.Net.Store
         private long bufferStart = 0; // position in file of buffer
         private int bufferPosition = 0; // position in buffer
         private readonly CRC32 crc;
+        private int disposed = 0; // LUCENENET specific - allow double-dispose
 
         /// <summary>
         /// Creates a new <see cref="BufferedIndexOutput"/> with the default buffer size
@@ -161,6 +163,8 @@ namespace Lucene.Net.Store
         /// <inheritdoc/>
         protected override void Dispose(bool disposing)
         {
+            if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
             if (disposing)
             {
                 Flush();
@@ -172,6 +176,7 @@ namespace Lucene.Net.Store
         [Obsolete("(4.1) this method will be removed in Lucene 5.0")]
         public override void Seek(long pos)
         {
+            EnsureOpen(); // LUCENENET specific - ensure we can't be abused after dispose
             Flush();
             bufferStart = pos;
         }
@@ -187,9 +192,22 @@ namespace Lucene.Net.Store
         {
             get
             {
+                EnsureOpen(); // LUCENENET specific - ensure we can't be abused after dispose
                 Flush();
                 return crc.Value;
             }
         }
+
+        // LUCENENET specific - ensure we can't be abused after dispose
+        private bool IsOpen => Interlocked.CompareExchange(ref this.disposed, 0, 0) == 0 ? true : false;
+
+        // LUCENENET specific - ensure we can't be abused after dispose
+        private void EnsureOpen()
+        {
+            if (!IsOpen)
+            {
+                throw AlreadyClosedException.Create(this.GetType().FullName, "this IndexOutput is disposed.");
+            }
+        }
     }
 }
\ No newline at end of file


[lucenenet] 01/07: BUG: Lucene.Net.Store.NRTCachingDirectory: Don't throw exceptions when Dispose() is called multiple times. Also added checks to ensure an ObjectDisposedException is thrown by any method call after Dispose() is called. Fixed all other directory implementations and made the check atomic so only the first call to Dispose() is run. Fixes #841. Related to #265.

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 9d9f1dbf6fd8154148ef5dc773fad0741d03ada9
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun May 14 19:48:23 2023 +0700

    BUG: Lucene.Net.Store.NRTCachingDirectory: Don't throw exceptions when Dispose() is called multiple times. Also added checks to ensure an ObjectDisposedException is thrown by any method call after Dispose() is called. Fixed all other directory implementations and made the check atomic so only the first call to Dispose() is run. Fixes #841. Related to #265.
---
 .../Store/BaseDirectoryTestCase.cs                 | 13 +++++
 .../Store/BaseDirectoryWrapper.cs                  | 59 ++++++++++++++++++++--
 .../Store/MockDirectoryWrapper.cs                  | 14 ++++-
 .../Support/TestApiConsistency.cs                  |  2 +-
 src/Lucene.Net.Tests/Store/TestDirectory.cs        | 14 +++++
 .../Store/TestNRTCachingDirectory.cs               | 13 +++++
 src/Lucene.Net.Tests/Support/TestApiConsistency.cs |  2 +-
 src/Lucene.Net/Store/BaseDirectory.cs              | 55 +++++++++++++++++---
 src/Lucene.Net/Store/CompoundFileDirectory.cs      | 11 ++--
 src/Lucene.Net/Store/FSDirectory.cs                |  5 +-
 src/Lucene.Net/Store/NRTCachingDirectory.cs        | 29 +++++++++--
 src/Lucene.Net/Store/RAMDirectory.cs               |  4 +-
 12 files changed, 190 insertions(+), 31 deletions(-)

diff --git a/src/Lucene.Net.TestFramework/Store/BaseDirectoryTestCase.cs b/src/Lucene.Net.TestFramework/Store/BaseDirectoryTestCase.cs
index 1609120c7..d08430688 100644
--- a/src/Lucene.Net.TestFramework/Store/BaseDirectoryTestCase.cs
+++ b/src/Lucene.Net.TestFramework/Store/BaseDirectoryTestCase.cs
@@ -1,6 +1,7 @@
 // Lucene version compatibility level 8.2.0
 // LUCENENET NOTE: This class now exists both here and in Lucene.Net.Tests
 using J2N.Threading;
+using Lucene.Net.Attributes;
 using Lucene.Net.Index;
 using Lucene.Net.Support;
 using Lucene.Net.Util;
@@ -455,6 +456,18 @@ namespace Lucene.Net.Store
             });
         }
 
+        /// <summary>
+        /// Make sure directory allows double-dispose as per the
+        /// <a href="https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern">dispose pattern docs</a>.
+        /// </summary>
+        [Test]
+        [LuceneNetSpecific] // GH-841, GH-265
+        public virtual void TestDoubleDispose()
+        {
+            using Directory dir = GetDirectory(CreateTempDir("testDoubleDispose"));
+            Assert.DoesNotThrow(() => dir.Dispose());
+        }
+
         //        private class ListAllThread : ThreadJob
         //        {
         //            private readonly BaseDirectoryTestCase outerInstance;
diff --git a/src/Lucene.Net.TestFramework/Store/BaseDirectoryWrapper.cs b/src/Lucene.Net.TestFramework/Store/BaseDirectoryWrapper.cs
index 8ac7921cc..ea72bc47b 100644
--- a/src/Lucene.Net.TestFramework/Store/BaseDirectoryWrapper.cs
+++ b/src/Lucene.Net.TestFramework/Store/BaseDirectoryWrapper.cs
@@ -1,5 +1,6 @@
-using Lucene.Net.Index;
+using Lucene.Net.Index;
 using Lucene.Net.Util;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -30,7 +31,48 @@ namespace Lucene.Net.Store
     {
         private bool checkIndexOnClose = true;
         private bool crossCheckTermVectorsOnClose = true;
-        private volatile bool isOpen = true; // LUCENENET specific - private because volatile is not CLS compliant, but made protected setter
+
+        // LUCENENET specific - setup to make it safe to call dispose multiple times
+        private const int True = 1;
+        private const int False = 0;
+
+        // LUCENENET specific - using Interlocked intead of a volatile field for IsOpen.
+        private int isOpen = True; // LUCENENET: Changed from bool to int so we can use Interlocked.
+
+        /// <summary>
+        /// Atomically sets the value to the given updated value
+        /// if the current value <c>==</c> the expected value.
+        /// <para/>
+        /// Expert: Use this in the <see cref="Dispose(bool)"/> call to skip
+        /// duplicate calls by using the folling if block to guard the
+        /// dispose logic.
+        /// <code>
+        /// protected override void Dispose(bool disposing)
+        /// {
+        ///     if (!CompareAndSetIsOpen(expect: true, update: false)) return;
+        /// 
+        ///     // Dispose unmanaged resources
+        ///     if (disposing)
+        ///     {
+        ///         // Dispose managed resources
+        ///     }
+        /// }
+        /// </code>
+        /// </summary>
+        /// <param name="expect">The expected value (the comparand).</param>
+        /// <param name="update">The new value.</param>
+        /// <returns><c>true</c> if successful. A <c>false</c> return value indicates that
+        /// the actual value was not equal to the expected value.</returns>
+        // LUCENENET specific - setup to make it safe to call dispose multiple times
+        protected internal bool CompareAndSetIsOpen(bool expect, bool update)
+        {
+            int e = expect ? True : False;
+            int u = update ? True : False;
+
+            int original = Interlocked.CompareExchange(ref isOpen, u, e);
+
+            return original == e;
+        }
 
         public BaseDirectoryWrapper(Directory @delegate)
             : base(@delegate)
@@ -39,9 +81,11 @@ namespace Lucene.Net.Store
 
         protected override void Dispose(bool disposing)
         {
+            if (!CompareAndSetIsOpen(expect: true, update: false)) return; // LUCENENET: allow dispose more than once as per https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern
+
             if (disposing)
             {
-                isOpen = false;
+                // LUCENENET: Removed setter for isOpen and put it above in the if check so it is atomic
                 if (checkIndexOnClose && DirectoryReader.IndexExists(this))
                 {
                     TestUtil.CheckIndex(this, crossCheckTermVectorsOnClose);
@@ -50,10 +94,15 @@ namespace Lucene.Net.Store
             }
         }
 
+        /// <summary>
+        /// Gets a value indicating whether the current <see cref="Directory"/> instance is open.
+        /// <para/>
+        /// Expert: This is useful for implementing the <see cref="EnsureOpen()"/> logic.
+        /// </summary>
         public virtual bool IsOpen
         {
-            get => isOpen;
-            protected set => isOpen = value; // LUCENENET specific - added protected setter and marked isOpen private because volatile is not CLS compliant
+            get => Interlocked.CompareExchange(ref isOpen, False, False) == True ? true : false;
+            protected set => Interlocked.Exchange(ref this.isOpen, value ? True : False); // LUCENENET specific - added protected setter
         }
 
         /// <summary>
diff --git a/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs b/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
index a5de3ab64..fc2b70004 100644
--- a/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
+++ b/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
@@ -919,6 +919,8 @@ namespace Lucene.Net.Store
 
         protected override void Dispose(bool disposing)
         {
+            if (!CompareAndSetIsOpen(expect: true, update: false)) return; // LUCENENET: allow dispose more than once as per https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern
+
             UninterruptableMonitor.Enter(this);
             try
             {
@@ -1097,8 +1099,16 @@ namespace Lucene.Net.Store
                             }
                         }
                     }
-                    m_input.Dispose(); // LUCENENET TODO: using blocks in this entire class
-                    throttledOutput.Dispose(); // LUCENENET specific
+                    // LUCENENET specific: While the Microsoft docs say that Dispose() should not throw errors,
+                    // we are being defensive because this is a test mock.
+                    try
+                    {
+                        m_input.Dispose();
+                    }
+                    finally
+                    {
+                        throttledOutput.Dispose(); // LUCENENET specific
+                    }
                 }
             }
             finally
diff --git a/src/Lucene.Net.Tests.TestFramework/Support/TestApiConsistency.cs b/src/Lucene.Net.Tests.TestFramework/Support/TestApiConsistency.cs
index 63d19514f..114619084 100644
--- a/src/Lucene.Net.Tests.TestFramework/Support/TestApiConsistency.cs
+++ b/src/Lucene.Net.Tests.TestFramework/Support/TestApiConsistency.cs
@@ -38,7 +38,7 @@ namespace Lucene.Net.Tests.TestFramework
         [TestCase(typeof(Lucene.Net.RandomExtensions))]
         public override void TestPrivateFieldNames(Type typeFromTargetAssembly)
         {
-            base.TestPrivateFieldNames(typeFromTargetAssembly, @"ApiScanTestBase|TestUtil\.MaxRecursionBound|Assert\.FailureFormat|Lucene\.Net\.Util\.RandomizedContext\.RandomizedContextPropertyName|Lucene\.Net\.Util\.DefaultNamespaceTypeWrapper\.AllMembers");
+            base.TestPrivateFieldNames(typeFromTargetAssembly, @"ApiScanTestBase|TestUtil\.MaxRecursionBound|Assert\.FailureFormat|Lucene\.Net\.Util\.RandomizedContext\.RandomizedContextPropertyName|Lucene\.Net\.Util\.DefaultNamespaceTypeWrapper\.AllMembers|^Lucene\.Net\.Store\.BaseDirectoryWrapper\.(?:True|False)");
         }
 
         [Test, LuceneNetSpecific]
diff --git a/src/Lucene.Net.Tests/Store/TestDirectory.cs b/src/Lucene.Net.Tests/Store/TestDirectory.cs
index 2ec815ea8..02339d6fd 100644
--- a/src/Lucene.Net.Tests/Store/TestDirectory.cs
+++ b/src/Lucene.Net.Tests/Store/TestDirectory.cs
@@ -57,6 +57,20 @@ namespace Lucene.Net.Store
             }
         }
 
+        [Test]
+        [LuceneNetSpecific] // GH-841, GH-265
+        public virtual void TestDoubleDispose()
+        {
+            DirectoryInfo tempDir = CreateTempDir(GetType().Name);
+            Directory[] dirs = new Directory[] { new RAMDirectory(), new SimpleFSDirectory(tempDir), new NIOFSDirectory(tempDir) };
+
+            foreach (Directory dir in dirs)
+            {
+                Assert.DoesNotThrow(() => dir.Dispose());
+                Assert.DoesNotThrow(() => dir.Dispose());
+            }
+        }
+
         // test is occasionally very slow, i dont know why
         // try this seed: 7D7E036AD12927F5:93333EF9E6DE44DE
         [Test]
diff --git a/src/Lucene.Net.Tests/Store/TestNRTCachingDirectory.cs b/src/Lucene.Net.Tests/Store/TestNRTCachingDirectory.cs
index 4844470ab..900da936b 100644
--- a/src/Lucene.Net.Tests/Store/TestNRTCachingDirectory.cs
+++ b/src/Lucene.Net.Tests/Store/TestNRTCachingDirectory.cs
@@ -5,6 +5,7 @@ using System.IO;
 using JCG = J2N.Collections.Generic;
 using Assert = Lucene.Net.TestFramework.Assert;
 using Console = Lucene.Net.Util.SystemConsole;
+using Lucene.Net.Attributes;
 
 namespace Lucene.Net.Store
 {
@@ -203,6 +204,18 @@ namespace Lucene.Net.Store
             newDir.Dispose();
         }
 
+        /// <summary>
+        /// Make sure directory allows double-dispose as per the
+        /// <a href="https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern">dispose pattern docs</a>.
+        /// </summary>
+        [Test]
+        [LuceneNetSpecific]
+        public virtual void TestDoubleDispose()
+        {
+            using Directory newDir = new NRTCachingDirectory(NewDirectory(), 2.0, 25.0);
+            Assert.DoesNotThrow(() => newDir.Dispose());
+        }
+
         /// <summary>
         /// Creates a file of the specified size with sequential data. The first
         ///  byte is written as the start byte provided. All subsequent bytes are
diff --git a/src/Lucene.Net.Tests/Support/TestApiConsistency.cs b/src/Lucene.Net.Tests/Support/TestApiConsistency.cs
index 75f8a15f4..f565676ac 100644
--- a/src/Lucene.Net.Tests/Support/TestApiConsistency.cs
+++ b/src/Lucene.Net.Tests/Support/TestApiConsistency.cs
@@ -38,7 +38,7 @@ namespace Lucene.Net
         [TestCase(typeof(Lucene.Net.Analysis.Analyzer))]
         public override void TestPrivateFieldNames(Type typeFromTargetAssembly)
         {
-            base.TestPrivateFieldNames(typeFromTargetAssembly, @"^Lucene\.Net\.Support\.(?:ConcurrentHashSet|PlatformHelper|DateTimeOffsetUtil|Arrays|IO\.FileSupport)|^Lucene\.ExceptionExtensions|^Lucene\.Net\.Util\.Constants\.MaxStackByteLimit|^Lucene\.Net\.Search\.TopDocs\.ShardByteSize");
+            base.TestPrivateFieldNames(typeFromTargetAssembly, @"^Lucene\.Net\.Support\.(?:ConcurrentHashSet|PlatformHelper|DateTimeOffsetUtil|Arrays|IO\.FileSupport)|^Lucene\.ExceptionExtensions|^Lucene\.Net\.Util\.Constants\.MaxStackByteLimit|^Lucene\.Net\.Search\.TopDocs\.ShardByteSize|^Lucene\.Net\.Store\.BaseDirectory\.(?:True|False)");
         }
 
         [Test, LuceneNetSpecific]
diff --git a/src/Lucene.Net/Store/BaseDirectory.cs b/src/Lucene.Net/Store/BaseDirectory.cs
index fe9745850..480ff8556 100644
--- a/src/Lucene.Net/Store/BaseDirectory.cs
+++ b/src/Lucene.Net/Store/BaseDirectory.cs
@@ -1,5 +1,6 @@
 using Lucene.Net.Diagnostics;
 using System;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -27,15 +28,57 @@ namespace Lucene.Net.Store
     /// </summary>
     public abstract class BaseDirectory : Directory
     {
-        private volatile bool isOpen = true;
+        // LUCENENET specific - setup to make it safe to call dispose multiple times
+        private const int True = 1;
+        private const int False = 0;
 
-        // LUCENENET specific - since we can't make a CLS-compliant 
-        // protected volatile field, we are exposing it through a protected
-        // property.
+        // LUCENENET specific - using Interlocked intead of a volatile field for IsOpen.
+        private int isOpen = True; // LUCENENET: Added check to ensure we aren't disposed.
+
+        /// <summary>
+        /// Gets a value indicating whether the current <see cref="Directory"/> instance is open.
+        /// <para/>
+        /// Expert: This is useful for implementing the <see cref="EnsureOpen()"/> logic.
+        /// </summary>
         protected internal virtual bool IsOpen
         {
-            get => isOpen;
-            set => isOpen = value;
+            get => Interlocked.CompareExchange(ref isOpen, False, False) == True ? true : false;
+            set => Interlocked.Exchange(ref this.isOpen, value ? True : False);
+        }
+
+        /// <summary>
+        /// Atomically sets the value to the given updated value
+        /// if the current value <c>==</c> the expected value.
+        /// <para/>
+        /// Expert: Use this in the <see cref="Dispose(bool)"/> call to skip
+        /// duplicate calls by using the folling if block to guard the
+        /// dispose logic.
+        /// <code>
+        /// protected override void Dispose(bool disposing)
+        /// {
+        ///     if (!CompareAndSetIsOpen(expect: true, update: false)) return;
+        /// 
+        ///     // Dispose unmanaged resources
+        ///     if (disposing)
+        ///     {
+        ///         // Dispose managed resources
+        ///     }
+        /// }
+        /// </code>
+        /// </summary>
+        /// <param name="expect">The expected value (the comparand).</param>
+        /// <param name="update">The new value.</param>
+        /// <returns><c>true</c> if successful. A <c>false</c> return value indicates that
+        /// the actual value was not equal to the expected value.</returns>
+        // LUCENENET specific - setup to make it safe to call dispose multiple times
+        protected internal bool CompareAndSetIsOpen(bool expect, bool update)
+        {
+            int e = expect ? True : False;
+            int u = update ? True : False;
+
+            int original = Interlocked.CompareExchange(ref isOpen, u, e);
+
+            return original == e;
         }
 
         /// <summary>
diff --git a/src/Lucene.Net/Store/CompoundFileDirectory.cs b/src/Lucene.Net/Store/CompoundFileDirectory.cs
index 9895c100f..72d25082b 100644
--- a/src/Lucene.Net/Store/CompoundFileDirectory.cs
+++ b/src/Lucene.Net/Store/CompoundFileDirectory.cs
@@ -286,17 +286,15 @@ namespace Lucene.Net.Store
 
         protected override void Dispose(bool disposing)
         {
+            // allow double close - usually to be consistent with other closeables
+            if (!CompareAndSetIsOpen(expect: true, update: false)) return; // already closed
+
             UninterruptableMonitor.Enter(this);
             try
             {
                 if (disposing)
                 {
-                    if (!IsOpen)
-                    {
-                        // allow double close - usually to be consistent with other closeables
-                        return; // already closed
-                    }
-                    IsOpen = false;
+                    // LUCENENET: Moved double-dispose logic outside of lock.
                     if (writer != null)
                     {
                         if (Debugging.AssertsEnabled) Debugging.Assert(openForWrite);
@@ -455,6 +453,7 @@ namespace Lucene.Net.Store
 
             protected override void Dispose(bool disposing)
             {
+                // LUCENENET: Intentionally blank
             }
 
             public override IndexInput OpenSlice(string sliceDescription, long offset, long length)
diff --git a/src/Lucene.Net/Store/FSDirectory.cs b/src/Lucene.Net/Store/FSDirectory.cs
index fee4ffe41..2439b8a92 100644
--- a/src/Lucene.Net/Store/FSDirectory.cs
+++ b/src/Lucene.Net/Store/FSDirectory.cs
@@ -423,10 +423,7 @@ namespace Lucene.Net.Store
         /// Closes the store to future operations. </summary>
         protected override void Dispose(bool disposing)
         {
-            if (disposing)
-            {
-                IsOpen = false;
-            }
+            IsOpen = false; // LUCENENET: Since there is nothing else to do here, we can safely call this. If we have other stuff to dispose, change to if (!CompareAndSetIsOpen(expect: true, update: false)) return;
         }
 
         /// <summary> the underlying filesystem directory </summary>
diff --git a/src/Lucene.Net/Store/NRTCachingDirectory.cs b/src/Lucene.Net/Store/NRTCachingDirectory.cs
index 94e9c5f3e..55ee2b3d8 100644
--- a/src/Lucene.Net/Store/NRTCachingDirectory.cs
+++ b/src/Lucene.Net/Store/NRTCachingDirectory.cs
@@ -2,7 +2,6 @@
 using Lucene.Net.Support.Threading;
 using System;
 using System.Collections.Generic;
-using System.IO;
 using Console = Lucene.Net.Util.SystemConsole;
 using JCG = J2N.Collections.Generic;
 
@@ -51,10 +50,10 @@ namespace Lucene.Net.Store
     /// <para/>Here's a simple example usage:
     ///
     /// <code>
-    ///     Directory fsDir = FSDirectory.Open(new DirectoryInfo("/path/to/index"));
-    ///     NRTCachingDirectory cachedFSDir = new NRTCachingDirectory(fsDir, 5.0, 60.0);
+    ///     using Directory fsDir = FSDirectory.Open(new DirectoryInfo("/path/to/index"));
+    ///     using NRTCachingDirectory cachedFSDir = new NRTCachingDirectory(fsDir, 5.0, 60.0);
     ///     IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_48, analyzer);
-    ///     IndexWriter writer = new IndexWriter(cachedFSDir, conf);
+    ///     using IndexWriter writer = new IndexWriter(cachedFSDir, conf);
     /// </code>
     ///
     /// <para>This will cache all newly flushed segments, all merges
@@ -64,7 +63,7 @@ namespace Lucene.Net.Store
     /// <para/>
     /// @lucene.experimental
     /// </summary>
-    public class NRTCachingDirectory : Directory
+    public class NRTCachingDirectory : BaseDirectory // LUCENENET specific - subclass BaseDirectory so we can leverage isOpen logic.
     {
         private readonly RAMDirectory cache = new RAMDirectory();
 
@@ -96,21 +95,25 @@ namespace Lucene.Net.Store
 
         public override void SetLockFactory(LockFactory lockFactory)
         {
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             @delegate.SetLockFactory(lockFactory);
         }
 
         public override string GetLockID()
         {
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             return @delegate.GetLockID();
         }
 
         public override Lock MakeLock(string name)
         {
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             return @delegate.MakeLock(name);
         }
 
         public override void ClearLock(string name)
         {
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             @delegate.ClearLock(name);
         }
 
@@ -124,6 +127,8 @@ namespace Lucene.Net.Store
             UninterruptableMonitor.Enter(this);
             try
             {
+                EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
+
                 ISet<string> files = new JCG.HashSet<string>();
                 foreach (string f in cache.ListAll())
                 {
@@ -166,6 +171,7 @@ namespace Lucene.Net.Store
         /// </summary>
         public virtual long GetSizeInBytes()
         {
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             return cache.GetSizeInBytes();
         }
 
@@ -175,6 +181,7 @@ namespace Lucene.Net.Store
             UninterruptableMonitor.Enter(this);
             try
             {
+                EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
                 return cache.FileExists(name) || @delegate.FileExists(name);
             }
             finally
@@ -188,6 +195,7 @@ namespace Lucene.Net.Store
             UninterruptableMonitor.Enter(this);
             try
             {
+                EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
                 if (VERBOSE)
                 {
                     Console.WriteLine("nrtdir.deleteFile name=" + name);
@@ -214,6 +222,7 @@ namespace Lucene.Net.Store
             UninterruptableMonitor.Enter(this);
             try
             {
+                EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
 #pragma warning disable 612, 618
                 if (cache.FileExists(name))
 #pragma warning restore 612, 618
@@ -233,6 +242,7 @@ namespace Lucene.Net.Store
 
         public virtual string[] ListCachedFiles()
         {
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             return cache.ListAll();
         }
 
@@ -242,6 +252,7 @@ namespace Lucene.Net.Store
             {
                 Console.WriteLine("nrtdir.createOutput name=" + name);
             }
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             if (DoCacheWrite(name, context))
             {
                 if (VERBOSE)
@@ -278,6 +289,7 @@ namespace Lucene.Net.Store
             {
                 Console.WriteLine("nrtdir.sync files=" + fileNames);
             }
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             foreach (string fileName in fileNames)
             {
                 UnCache(fileName);
@@ -290,10 +302,12 @@ namespace Lucene.Net.Store
             UninterruptableMonitor.Enter(this);
             try
             {
+                EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
                 if (VERBOSE)
                 {
                     Console.WriteLine("nrtdir.openInput name=" + name);
                 }
+                
 #pragma warning disable 612, 618
                 if (cache.FileExists(name))
 #pragma warning restore 612, 618
@@ -352,6 +366,8 @@ namespace Lucene.Net.Store
         /// </summary>
         protected override void Dispose(bool disposing)
         {
+            if (!CompareAndSetIsOpen(expect: true, update: false)) return; // LUCENENET: Don't allow dispose more than once
+
             if (disposing)
             {
                 // NOTE: technically we shouldn't have to do this, ie,
@@ -374,6 +390,7 @@ namespace Lucene.Net.Store
         /// </summary>
         protected virtual bool DoCacheWrite(string name, IOContext context)
         {
+            EnsureOpen(); // LUCENENET: Added check to ensure we aren't disposed.
             //System.out.println(Thread.currentThread().getName() + ": CACHE check merge=" + merge + " size=" + (merge==null ? 0 : merge.estimatedMergeBytes));
 
             long bytes = 0;
@@ -402,6 +419,8 @@ namespace Lucene.Net.Store
                 {
                     Console.WriteLine("nrtdir.unCache name=" + fileName);
                 }
+                // LUCENENET: We delegate the EnsureOpen() call to cache.FileExists() here so we can
+                // call this after setting isDisposed to true.
 #pragma warning disable 612, 618
                 if (!cache.FileExists(fileName))
 #pragma warning restore 612, 618
diff --git a/src/Lucene.Net/Store/RAMDirectory.cs b/src/Lucene.Net/Store/RAMDirectory.cs
index 6383f9bf9..67c956ce9 100644
--- a/src/Lucene.Net/Store/RAMDirectory.cs
+++ b/src/Lucene.Net/Store/RAMDirectory.cs
@@ -217,9 +217,11 @@ namespace Lucene.Net.Store
         /// Closes the store to future operations, releasing associated memory. </summary>
         protected override void Dispose(bool disposing)
         {
+            if (!CompareAndSetIsOpen(expect: true, update: false)) return; // LUCENENET: allow dispose more than once as per https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern
+
             if (disposing)
             {
-                IsOpen = false;
+                // LUCENENET: Removed setter for isOpen and put it above in the if check so it is atomic
                 m_fileMap.Clear();
             }
         }


[lucenenet] 06/07: Lucene.Net.Store.InputStreamDataInput: Allow double-dispose calls and guard against usage after Dispose(). See #265.

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 5336dfc16e38c9ced9dbc3b4ab138cc0828e507e
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun May 14 23:03:28 2023 +0700

    Lucene.Net.Store.InputStreamDataInput: Allow double-dispose calls and guard against usage after Dispose(). See #265.
---
 src/Lucene.Net/Store/InputStreamDataInput.cs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/Lucene.Net/Store/InputStreamDataInput.cs b/src/Lucene.Net/Store/InputStreamDataInput.cs
index b9462397a..8b450561b 100644
--- a/src/Lucene.Net/Store/InputStreamDataInput.cs
+++ b/src/Lucene.Net/Store/InputStreamDataInput.cs
@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -26,6 +27,7 @@ namespace Lucene.Net.Store
     public class InputStreamDataInput : DataInput, IDisposable
     {
         private readonly Stream _is;
+        private int disposed = 0; // LUCENENET specific - allow double-dispose
 
         public InputStreamDataInput(Stream @is)
         {
@@ -65,6 +67,8 @@ namespace Lucene.Net.Store
 
         protected virtual void Dispose(bool disposing)
         {
+            if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
             if (disposing)
             {
                 _is.Dispose();


[lucenenet] 02/07: Lucene.Net.Store (Directory + MMapDirectory + NIOFSDirectory + SimpleFSDirectory): Upgraded all FSDirectories to allow multiple Dispose() calls on IndexInputSlicer and BufferedIndexInput subclasses. See #265.

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit c49fd9a26f027ab9b16123d2fc07f144ee97e694
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun May 14 20:32:50 2023 +0700

    Lucene.Net.Store (Directory + MMapDirectory + NIOFSDirectory + SimpleFSDirectory): Upgraded all FSDirectories to allow multiple Dispose() calls on IndexInputSlicer and BufferedIndexInput subclasses. See #265.
---
 src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs | 13 ++++++-------
 src/Lucene.Net/Store/Directory.cs                          |  7 +++++++
 src/Lucene.Net/Store/MMapDirectory.cs                      |  8 +++++++-
 src/Lucene.Net/Store/NIOFSDirectory.cs                     |  8 ++++++++
 src/Lucene.Net/Store/SimpleFSDirectory.cs                  |  8 ++++++++
 5 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs b/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
index fc2b70004..dc226fa69 100644
--- a/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
+++ b/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
@@ -1399,17 +1399,16 @@ namespace Lucene.Net.Store
                 this.delegateHandle = delegateHandle;
             }
 
-            private int disposed = 0;
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
 
             protected override void Dispose(bool disposing)
             {
-                if (0 == Interlocked.CompareExchange(ref this.disposed, 1, 0))
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
+                if (disposing)
                 {
-                    if (disposing)
-                    {
-                        delegateHandle.Dispose();
-                        outerInstance.RemoveOpenFile(this, name);
-                    }
+                    delegateHandle.Dispose();
+                    outerInstance.RemoveOpenFile(this, name);
                 }
             }
 
diff --git a/src/Lucene.Net/Store/Directory.cs b/src/Lucene.Net/Store/Directory.cs
index f84a16a4c..f58bfc864 100644
--- a/src/Lucene.Net/Store/Directory.cs
+++ b/src/Lucene.Net/Store/Directory.cs
@@ -2,6 +2,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -253,6 +254,7 @@ namespace Lucene.Net.Store
         private sealed class IndexInputSlicerAnonymousClass : IndexInputSlicer
         {
             private readonly IndexInput @base;
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
 
             public IndexInputSlicerAnonymousClass(IndexInput @base)
             {
@@ -266,6 +268,8 @@ namespace Lucene.Net.Store
 
             protected override void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 if (disposing)
                 {
                     @base.Dispose();
@@ -323,6 +327,7 @@ namespace Lucene.Net.Store
             private IndexInput @base;
             private long fileOffset;
             private long length;
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
 
             internal SlicedIndexInput(string sliceDescription, IndexInput @base, long fileOffset, long length)
                 : this(sliceDescription, @base, fileOffset, length, BufferedIndexInput.BUFFER_SIZE)
@@ -377,6 +382,8 @@ namespace Lucene.Net.Store
             /// </summary>
             protected override void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 if (disposing)
                 {
                     @base.Dispose();
diff --git a/src/Lucene.Net/Store/MMapDirectory.cs b/src/Lucene.Net/Store/MMapDirectory.cs
index 5a87d2a74..ea5e6a0f9 100644
--- a/src/Lucene.Net/Store/MMapDirectory.cs
+++ b/src/Lucene.Net/Store/MMapDirectory.cs
@@ -5,6 +5,7 @@ using Lucene.Net.Diagnostics;
 using System;
 using System.IO;
 using System.IO.MemoryMappedFiles;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -196,8 +197,8 @@ namespace Lucene.Net.Store
         private sealed class IndexInputSlicerAnonymousClass : IndexInputSlicer
         {
             private readonly MMapDirectory outerInstance;
-
             private readonly MMapIndexInput full;
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
 
             public IndexInputSlicerAnonymousClass(MMapDirectory outerInstance, MMapIndexInput full)
             {
@@ -220,6 +221,8 @@ namespace Lucene.Net.Store
 
             protected override void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 if (disposing)
                 {
                     full.Dispose();
@@ -231,6 +234,7 @@ namespace Lucene.Net.Store
         {
             internal MemoryMappedFile memoryMappedFile; // .NET port: this is equivalent to FileChannel.map
             private readonly FileStream fc;
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
 
             internal MMapIndexInput(MMapDirectory outerInstance, string resourceDescription, FileStream fc)
                 : base(resourceDescription, null, fc.Length, outerInstance.chunkSizePower, true)
@@ -241,6 +245,8 @@ namespace Lucene.Net.Store
 
             protected override sealed void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 try
                 {
                     if (disposing)
diff --git a/src/Lucene.Net/Store/NIOFSDirectory.cs b/src/Lucene.Net/Store/NIOFSDirectory.cs
index 0b918ddb9..684f42b68 100644
--- a/src/Lucene.Net/Store/NIOFSDirectory.cs
+++ b/src/Lucene.Net/Store/NIOFSDirectory.cs
@@ -3,6 +3,7 @@ using Lucene.Net.Diagnostics;
 using Lucene.Net.Support.IO;
 using System;
 using System.IO;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -119,6 +120,7 @@ namespace Lucene.Net.Store
             private readonly IOContext context;
             private readonly FileInfo path;
             private readonly FileStream descriptor;
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
 
             public IndexInputSlicerAnonymousClass(IOContext context, FileInfo path, FileStream descriptor)
             {
@@ -129,6 +131,8 @@ namespace Lucene.Net.Store
 
             protected override void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 if (disposing)
                 {
                     descriptor.Dispose();
@@ -184,6 +188,8 @@ namespace Lucene.Net.Store
 
             private ByteBuffer byteBuf; // wraps the buffer for NIO
 
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
+
             public NIOFSIndexInput(string resourceDesc, FileStream fc, IOContext context)
                 : base(resourceDesc, context)
             {
@@ -203,6 +209,8 @@ namespace Lucene.Net.Store
 
             protected override void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 if (disposing && !isClone)
                 {
                     m_channel.Dispose();
diff --git a/src/Lucene.Net/Store/SimpleFSDirectory.cs b/src/Lucene.Net/Store/SimpleFSDirectory.cs
index 61536f68c..923ca685b 100644
--- a/src/Lucene.Net/Store/SimpleFSDirectory.cs
+++ b/src/Lucene.Net/Store/SimpleFSDirectory.cs
@@ -2,6 +2,7 @@
 using Lucene.Net.Support.Threading;
 using System;
 using System.IO;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -115,6 +116,7 @@ namespace Lucene.Net.Store
             private readonly IOContext context;
             private readonly FileInfo file;
             private readonly FileStream descriptor;
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
 
             public IndexInputSlicerAnonymousClass(IOContext context, FileInfo file, FileStream descriptor)
             {
@@ -125,6 +127,8 @@ namespace Lucene.Net.Store
 
             protected override void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 if (disposing)
                 {
                     descriptor.Dispose();
@@ -156,6 +160,8 @@ namespace Lucene.Net.Store
         /// </summary>
         protected internal class SimpleFSIndexInput : BufferedIndexInput
         {
+            private int disposed = 0; // LUCENENET specific - allow double-dispose
+
             // LUCENENET specific: chunk size not needed
             ///// <summary>
             ///// The maximum chunk size is 8192 bytes, because <seealso cref="RandomAccessFile"/> mallocs
@@ -199,6 +205,8 @@ namespace Lucene.Net.Store
 
             protected override void Dispose(bool disposing)
             {
+                if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
                 if (disposing && !IsClone)
                 {
                     m_file.Dispose();


[lucenenet] 07/07: Lucene.Net.Store.OutputStreamDataOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit e9c47b2dea45097a2761ebf2b3695adc456e8f26
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun May 14 23:04:14 2023 +0700

    Lucene.Net.Store.OutputStreamDataOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.
---
 src/Lucene.Net/Store/OutputStreamDataOutput.cs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/Lucene.Net/Store/OutputStreamDataOutput.cs b/src/Lucene.Net/Store/OutputStreamDataOutput.cs
index f635a2b36..7a96de6a8 100644
--- a/src/Lucene.Net/Store/OutputStreamDataOutput.cs
+++ b/src/Lucene.Net/Store/OutputStreamDataOutput.cs
@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -26,6 +27,7 @@ namespace Lucene.Net.Store
     public class OutputStreamDataOutput : DataOutput, IDisposable
     {
         private readonly Stream _os;
+        private int disposed = 0; // LUCENENET specific - allow double-dispose
 
         public OutputStreamDataOutput(Stream os)
         {
@@ -60,6 +62,8 @@ namespace Lucene.Net.Store
         // LUCENENET specific - implemented proper dispose pattern
         protected virtual void Dispose(bool disposing)
         {
+            if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
             if (disposing)
             {
                 _os.Dispose();


[lucenenet] 05/07: Lucene.Net.Store.RateLimitedIndexOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit a9c8388aa9fc696ec14f1aa88daa0512f804be32
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun May 14 23:02:52 2023 +0700

    Lucene.Net.Store.RateLimitedIndexOutput: Allow double-dispose calls and guard against usage after Dispose(). See #265.
---
 src/Lucene.Net/Store/RateLimitedIndexOutput.cs | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/Lucene.Net/Store/RateLimitedIndexOutput.cs b/src/Lucene.Net/Store/RateLimitedIndexOutput.cs
index 92ea575b6..fe0a147ae 100644
--- a/src/Lucene.Net/Store/RateLimitedIndexOutput.cs
+++ b/src/Lucene.Net/Store/RateLimitedIndexOutput.cs
@@ -1,5 +1,6 @@
-using System;
+using System;
 using System.Runtime.CompilerServices;
+using System.Threading;
 
 namespace Lucene.Net.Store
 {
@@ -30,6 +31,7 @@ namespace Lucene.Net.Store
         private readonly IndexOutput @delegate;
         private readonly BufferedIndexOutput bufferedDelegate;
         private readonly RateLimiter rateLimiter;
+        private int disposed = 0; // LUCENENET specific - allow double-dispose
 
         internal RateLimitedIndexOutput(RateLimiter rateLimiter, IndexOutput @delegate)
         {
@@ -62,15 +64,21 @@ namespace Lucene.Net.Store
 
         public override long Length
         {
-            get => @delegate.Length;
+            get
+            {
+                EnsureOpen(); // LUCENENET specific - ensure we can't be abused after dispose
+                return @delegate.Length;
+            }
             set
             {
+                // LUCENENET: Intentionally blank
             }
         }
 
         [Obsolete("(4.1) this method will be removed in Lucene 5.0")]
         public override void Seek(long pos)
         {
+            EnsureOpen(); // LUCENENET specific - ensure we can't be abused after dispose
             Flush();
             @delegate.Seek(pos);
         }
@@ -90,6 +98,8 @@ namespace Lucene.Net.Store
 
         protected override void Dispose(bool disposing)
         {
+            if (0 != Interlocked.CompareExchange(ref this.disposed, 1, 0)) return; // LUCENENET specific - allow double-dispose
+
             if (disposing)
             {
                 try
@@ -102,5 +112,17 @@ namespace Lucene.Net.Store
                 }
             }
         }
+
+        // LUCENENET specific - ensure we can't be abused after dispose
+        private bool IsOpen => Interlocked.CompareExchange(ref this.disposed, 0, 0) == 0 ? true : false;
+
+        // LUCENENET specific - ensure we can't be abused after dispose
+        private void EnsureOpen()
+        {
+            if (!IsOpen)
+            {
+                throw AlreadyClosedException.Create(this.GetType().FullName, "this IndexOutput is disposed.");
+            }
+        }
     }
 }
\ No newline at end of file