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

shazwazza pushed a commit to branch bugfix/308-TestAddIndexesWithCloseNoWait
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 717d7e5f96f8889904ee6902553141bebee0684b
Author: Shannon <[email protected]>
AuthorDate: Tue Jul 21 13:04:08 2020 +1000

    Fixes deadlock issues with MockDirectoryWrapper
---
 .../Store/MockDirectoryWrapper.cs                  | 515 ++++++++++++---------
 .../FindFirstFailingSeedAttribute.cs               |  97 ++++
 src/Lucene.Net.Tests/Index/TestAddIndexes.cs       |  14 +
 3 files changed, 412 insertions(+), 214 deletions(-)

diff --git a/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs 
b/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
index ff08f44..691ffb4 100644
--- a/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
+++ b/src/Lucene.Net.TestFramework/Store/MockDirectoryWrapper.cs
@@ -93,6 +93,8 @@ namespace Lucene.Net.Store
     /// </summary>
     public class MockDirectoryWrapper : BaseDirectoryWrapper
     {
+        private object locker = new object();
+
         internal long maxSize;
 
         // Max actual bytes used. this is set by MockRAMOutputStream:
@@ -135,22 +137,30 @@ namespace Lucene.Net.Store
 
         private void Init()
         {
-            lock (this)
+            lock (locker)
             {
-                if (openFiles == null)
-                {
-                    openFiles = new Dictionary<string, 
int>(StringComparer.Ordinal);
-                    openFilesDeleted = new 
JCG.HashSet<string>(StringComparer.Ordinal);
-                }
+                InitLocked();
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private void InitLocked()
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new InvalidOperationException($"{nameof(InitLocked)} 
must be called within the lock {nameof(locker)}");
 
-                if (createdFiles == null)
-                {
-                    createdFiles = new 
JCG.HashSet<string>(StringComparer.Ordinal);
-                }
-                if (unSyncedFiles == null)
-                {
-                    unSyncedFiles = new 
JCG.HashSet<string>(StringComparer.Ordinal);
-                }
+            if (openFiles == null)
+            {
+                openFiles = new Dictionary<string, 
int>(StringComparer.Ordinal);
+                openFilesDeleted = new 
JCG.HashSet<string>(StringComparer.Ordinal);
+            }
+
+            if (createdFiles == null)
+            {
+                createdFiles = new JCG.HashSet<string>(StringComparer.Ordinal);
+            }
+            if (unSyncedFiles == null)
+            {
+                unSyncedFiles = new 
JCG.HashSet<string>(StringComparer.Ordinal);
             }
         }
 
@@ -234,10 +244,10 @@ namespace Lucene.Net.Store
         [MethodImpl(MethodImplOptions.NoInlining)]
         public override void Sync(ICollection<string> names)
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
-                MaybeThrowDeterministicException();
+                MaybeThrowDeterministicExceptionLocked();
                 if (crashed)
                 {
                     throw new IOException("cannot sync after crash");
@@ -262,22 +272,30 @@ namespace Lucene.Net.Store
 
         public long GetSizeInBytes()
         {
-            lock (this)
+            lock (locker)
             {
-                if (m_input is RAMDirectory)
-                {
-                    return ((RAMDirectory)m_input).GetSizeInBytes();
-                }
-                else
+                return GetSizeInBytesLocked();
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private long GetSizeInBytesLocked()
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(GetSizeInBytesLocked)} must be called 
within the lock {nameof(locker)}");
+
+            if (m_input is RAMDirectory)
+            {
+                return ((RAMDirectory)m_input).GetSizeInBytes();
+            }
+            else
+            {
+                // hack
+                long size = 0;
+                foreach (string file in m_input.ListAll())
                 {
-                    // hack
-                    long size = 0;
-                    foreach (string file in m_input.ListAll())
-                    {
-                        size += m_input.FileLength(file);
-                    }
-                    return size;
+                    size += m_input.FileLength(file);
                 }
+                return size;
             }
         }
 
@@ -287,123 +305,131 @@ namespace Lucene.Net.Store
         /// </summary>
         public virtual void Crash()
         {
-            lock (this)
+            lock (locker)
             {
-                crashed = true;
-                openFiles = new Dictionary<string, 
int>(StringComparer.Ordinal);
-                openFilesForWrite = new 
JCG.HashSet<string>(StringComparer.Ordinal);
-                openFilesDeleted = new 
JCG.HashSet<string>(StringComparer.Ordinal);
-                using (IEnumerator<string> it = unSyncedFiles.GetEnumerator())
+                CrashLocked();
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private void CrashLocked()
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new InvalidOperationException($"{nameof(CrashLocked)} 
must be called within the lock {nameof(locker)}");
+
+            crashed = true;
+            openFiles = new Dictionary<string, int>(StringComparer.Ordinal);
+            openFilesForWrite = new 
JCG.HashSet<string>(StringComparer.Ordinal);
+            openFilesDeleted = new JCG.HashSet<string>(StringComparer.Ordinal);
+            using (IEnumerator<string> it = unSyncedFiles.GetEnumerator())
+            {
+                unSyncedFiles = new 
JCG.HashSet<string>(StringComparer.Ordinal);
+                // first force-close all files, so we can corrupt on windows 
etc.
+                // clone the file map, as these guys want to remove themselves 
on close.
+                var m = new JCG.Dictionary<IDisposable, 
Exception>(openFileHandles, IdentityEqualityComparer<IDisposable>.Default);
+                foreach (IDisposable f in m.Keys)
                 {
-                    unSyncedFiles = new 
JCG.HashSet<string>(StringComparer.Ordinal);
-                    // first force-close all files, so we can corrupt on 
windows etc.
-                    // clone the file map, as these guys want to remove 
themselves on close.
-                    var m = new JCG.Dictionary<IDisposable, 
Exception>(openFileHandles, IdentityEqualityComparer<IDisposable>.Default);
-                    foreach (IDisposable f in m.Keys)
+                    try
                     {
-                        try
-                        {
-                            f.Dispose();
-                        }
+                        f.Dispose();
+                    }
 #pragma warning disable 168
-                        catch (Exception ignored)
+                    catch (Exception ignored)
 #pragma warning restore 168
-                        {
-                            //Debug.WriteLine("Crash(): f.Dispose() FAILED for 
{0}:\n{1}", f.ToString(), ignored.ToString());
-                        }
+                    {
+                        //Debug.WriteLine("Crash(): f.Dispose() FAILED for 
{0}:\n{1}", f.ToString(), ignored.ToString());
                     }
+                }
 
-                    while (it.MoveNext())
-                    {
-                        string name = it.Current;
-                        int damage = randomState.Next(5);
-                        string action = null;
+                while (it.MoveNext())
+                {
+                    string name = it.Current;
+                    int damage = randomState.Next(5);
+                    string action = null;
 
-                        if (damage == 0)
-                        {
-                            action = "deleted";
-                            DeleteFile(name, true);
-                        }
-                        else if (damage == 1)
+                    if (damage == 0)
+                    {
+                        action = "deleted";
+                        DeleteFileLocked(name, true);
+                    }
+                    else if (damage == 1)
+                    {
+                        action = "zeroed";
+                        // Zero out file entirely
+                        long length = FileLength(name);
+                        var zeroes = new byte[256];
+                        long upto = 0;
+                        using (IndexOutput @out = m_input.CreateOutput(name, 
LuceneTestCase.NewIOContext(randomState)))
                         {
-                            action = "zeroed";
-                            // Zero out file entirely
-                            long length = FileLength(name);
-                            var zeroes = new byte[256];
-                            long upto = 0;
-                            using (IndexOutput @out = 
m_input.CreateOutput(name, LuceneTestCase.NewIOContext(randomState)))
+                            while (upto < length)
                             {
-                                while (upto < length)
-                                {
-                                    var limit = (int)Math.Min(length - upto, 
zeroes.Length);
-                                    @out.WriteBytes(zeroes, 0, limit);
-                                    upto += limit;
-                                }
+                                var limit = (int)Math.Min(length - upto, 
zeroes.Length);
+                                @out.WriteBytes(zeroes, 0, limit);
+                                upto += limit;
                             }
                         }
-                        else if (damage == 2)
-                        {
-                            action = "partially truncated";
-                            // Partially Truncate the file:
+                    }
+                    else if (damage == 2)
+                    {
+                        action = "partially truncated";
+                        // Partially Truncate the file:
 
-                            // First, make temp file and copy only half this
-                            // file over:
-                            string tempFileName;
-                            while (true)
+                        // First, make temp file and copy only half this
+                        // file over:
+                        string tempFileName;
+                        while (true)
+                        {
+                            tempFileName = "" + randomState.Next();
+                            if (!LuceneTestCase.SlowFileExists(m_input, 
tempFileName))
                             {
-                                tempFileName = "" + randomState.Next();
-                                if (!LuceneTestCase.SlowFileExists(m_input, 
tempFileName))
-                                {
-                                    break;
-                                }
+                                break;
                             }
-                            using (IndexOutput tempOut = 
m_input.CreateOutput(tempFileName, LuceneTestCase.NewIOContext(randomState)))
+                        }
+                        using (IndexOutput tempOut = 
m_input.CreateOutput(tempFileName, LuceneTestCase.NewIOContext(randomState)))
+                        {
+                            using (IndexInput ii = m_input.OpenInput(name, 
LuceneTestCase.NewIOContext(randomState)))
                             {
-                                using (IndexInput ii = m_input.OpenInput(name, 
LuceneTestCase.NewIOContext(randomState)))
-                                {
-                                    tempOut.CopyBytes(ii, ii.Length / 2);
-                                }
+                                tempOut.CopyBytes(ii, ii.Length / 2);
                             }
+                        }
 
-                            // Delete original and copy bytes back:
-                            DeleteFile(name, true);
+                        // Delete original and copy bytes back:
+                        DeleteFileLocked(name, true);
 
-                            using (IndexOutput @out = 
m_input.CreateOutput(name, LuceneTestCase.NewIOContext(randomState)))
-                            {
-                                using (IndexInput ii = 
m_input.OpenInput(tempFileName, LuceneTestCase.NewIOContext(randomState)))
-                                {
-                                    @out.CopyBytes(ii, ii.Length);
-                                }
-                            }
-                            DeleteFile(tempFileName, true);
-                        }
-                        else if (damage == 3)
+                        using (IndexOutput @out = m_input.CreateOutput(name, 
LuceneTestCase.NewIOContext(randomState)))
                         {
-                            // The file survived intact:
-                            action = "didn't change";
-                        }
-                        else
-                        {
-                            action = "fully truncated";
-                            // Totally truncate the file to zero bytes
-                            DeleteFile(name, true);
-                            using (IndexOutput @out = 
m_input.CreateOutput(name, LuceneTestCase.NewIOContext(randomState)))
+                            using (IndexInput ii = 
m_input.OpenInput(tempFileName, LuceneTestCase.NewIOContext(randomState)))
                             {
-                                @out.Length = 0;
+                                @out.CopyBytes(ii, ii.Length);
                             }
                         }
-                        if (LuceneTestCase.Verbose)
+                        DeleteFileLocked(tempFileName, true);
+                    }
+                    else if (damage == 3)
+                    {
+                        // The file survived intact:
+                        action = "didn't change";
+                    }
+                    else
+                    {
+                        action = "fully truncated";
+                        // Totally truncate the file to zero bytes
+                        DeleteFileLocked(name, true);
+                        using (IndexOutput @out = m_input.CreateOutput(name, 
LuceneTestCase.NewIOContext(randomState)))
                         {
-                            Console.WriteLine("MockDirectoryWrapper: " + 
action + " unsynced file: " + name);
+                            @out.Length = 0;
                         }
                     }
+                        if (LuceneTestCase.Verbose)
+                    {
+                        Console.WriteLine("MockDirectoryWrapper: " + action + 
" unsynced file: " + name);
+                    }
                 }
             }
         }
 
         public virtual void ClearCrash()
         {
-            lock (this)
+            lock (locker)
             {
                 crashed = false;
                 openLocks.Clear();
@@ -506,34 +532,51 @@ namespace Lucene.Net.Store
         [MethodImpl(MethodImplOptions.NoInlining)]
         public override void DeleteFile(string name)
         {
-            lock (this)
+            lock (locker)
             {
-                MaybeYield();
-                DeleteFile(name, false);
+                DeleteFileLocked(name);
             }
         }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private void DeleteFileLocked(string name)
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(DeleteFileLocked)} must be called within 
the lock {nameof(locker)}");
+
+            MaybeYield();
+            DeleteFileLocked(name, false);
+        }
+
 
         // if there are any exceptions in OpenFileHandles
         // capture those as inner exceptions
         private Exception WithAdditionalErrorInformation(Exception t, string 
name, bool input)
         {
-            lock (this)
+            lock(locker)
             {
-                foreach (var ent in openFileHandles)
+                return WithAdditionalErrorInformationLocked(t, name, input);
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private Exception WithAdditionalErrorInformationLocked(Exception t, 
string name, bool input)
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(WithAdditionalErrorInformationLocked)} must 
be called within the lock {nameof(locker)}");
+
+            foreach (var ent in openFileHandles)
+            {
+                if (input && ent.Key is MockIndexInputWrapper && 
((MockIndexInputWrapper)ent.Key).name.Equals(name, StringComparison.Ordinal))
                 {
-                    if (input && ent.Key is MockIndexInputWrapper && 
((MockIndexInputWrapper)ent.Key).name.Equals(name, StringComparison.Ordinal))
-                    {
-                        t = CreateException(t, ent.Value);
-                        break;
-                    }
-                    else if (!input && ent.Key is MockIndexOutputWrapper && 
((MockIndexOutputWrapper)ent.Key).name.Equals(name, StringComparison.Ordinal))
-                    {
-                        t = CreateException(t, ent.Value);
-                        break;
-                    }
+                    t = CreateException(t, ent.Value);
+                    break;
+                }
+                else if (!input && ent.Key is MockIndexOutputWrapper && 
((MockIndexOutputWrapper)ent.Key).name.Equals(name, StringComparison.Ordinal))
+                {
+                    t = CreateException(t, ent.Value);
+                    break;
                 }
-                return t;
             }
+            return t;
         }
 
         private Exception CreateException(Exception exception, Exception 
innerException)
@@ -556,48 +599,57 @@ namespace Lucene.Net.Store
         [MethodImpl(MethodImplOptions.NoInlining)]
         private void DeleteFile(string name, bool forced)
         {
-            lock (this)
+            lock (locker)
             {
-                MaybeYield();
+                DeleteFileLocked(name, forced);
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void DeleteFileLocked(string name, bool forced)
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(DeleteFileLocked)} must be called within 
the lock {nameof(locker)}");
 
-                MaybeThrowDeterministicException();
+            MaybeYield();
 
-                if (crashed && !forced)
-                {
-                    throw new IOException("cannot delete after crash");
-                }
+            MaybeThrowDeterministicExceptionLocked();
 
-                if (unSyncedFiles.Contains(name))
-                {
-                    unSyncedFiles.Remove(name);
-                }
-                if (!forced && (noDeleteOpenFile || assertNoDeleteOpenFile))
+            if (crashed && !forced)
+            {
+                throw new IOException("cannot delete after crash");
+            }
+
+            if (unSyncedFiles.Contains(name))
+            {
+                unSyncedFiles.Remove(name);
+            }
+            if (!forced && (noDeleteOpenFile || assertNoDeleteOpenFile))
+            {
+                if (openFiles.ContainsKey(name))
                 {
-                    if (openFiles.ContainsKey(name))
-                    {
-                        openFilesDeleted.Add(name);
+                    openFilesDeleted.Add(name);
 
-                        if (!assertNoDeleteOpenFile)
-                        {
-                            throw WithAdditionalErrorInformation(new 
IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot 
delete"), name, true);
-                        }
-                        else
-                        {
-                            throw WithAdditionalErrorInformation(new 
AssertionError("MockDirectoryWrapper: file \"" + name + "\" is still open: 
cannot delete"), name, true);
-                        }
+                    if (!assertNoDeleteOpenFile)
+                    {
+                        throw WithAdditionalErrorInformationLocked(new 
IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot 
delete"), name, true);
                     }
                     else
                     {
-                        openFilesDeleted.Remove(name);
+                        throw WithAdditionalErrorInformationLocked(new 
AssertionError("MockDirectoryWrapper: file \"" + name + "\" is still open: 
cannot delete"), name, true);
                     }
                 }
-                m_input.DeleteFile(name);
+                else
+                {
+                    openFilesDeleted.Remove(name);
+                }
             }
+            m_input.DeleteFile(name);
         }
 
         public virtual ICollection<string> GetOpenDeletedFiles()
         {
-            lock (this)
+            lock (locker)
             {
                 return new JCG.HashSet<string>(openFilesDeleted, 
StringComparer.Ordinal);
             }
@@ -613,27 +665,27 @@ namespace Lucene.Net.Store
 
         public override IndexOutput CreateOutput(string name, IOContext 
context)
         {
-            lock (this)
+            lock (locker)
             {
-                MaybeThrowDeterministicException();
+                MaybeThrowDeterministicExceptionLocked();
                 MaybeThrowIOExceptionOnOpen(name);
                 MaybeYield();
                 if (failOnCreateOutput)
                 {
-                    MaybeThrowDeterministicException();
+                    MaybeThrowDeterministicExceptionLocked();
                 }
                 if (crashed)
                 {
                     throw new IOException("cannot createOutput after crash");
                 }
-                Init();
-                lock (this)
+
+                InitLocked();
+
+                if (preventDoubleWrite && createdFiles.Contains(name) && 
!name.Equals("segments.gen", StringComparison.Ordinal))
                 {
-                    if (preventDoubleWrite && createdFiles.Contains(name) && 
!name.Equals("segments.gen", StringComparison.Ordinal))
-                    {
-                        throw new IOException("file \"" + name + "\" was 
already written to");
-                    }
+                    throw new IOException("file \"" + name + "\" was already 
written to");
                 }
+
                 if ((noDeleteOpenFile || assertNoDeleteOpenFile) && 
openFiles.ContainsKey(name))
                 {
                     if (!assertNoDeleteOpenFile)
@@ -682,7 +734,7 @@ namespace Lucene.Net.Store
                     delegateOutput = new BufferedIndexOutputWrapper(1 + 
randomState.Next(BufferedIndexOutput.DEFAULT_BUFFER_SIZE), delegateOutput);
                 }
                 IndexOutput io = new MockIndexOutputWrapper(this, 
delegateOutput, name);
-                AddFileHandle(io, name, Handle.Output);
+                AddFileHandleLocked(io, name, Handle.Output);
                 openFilesForWrite.Add(name);
 
                 // throttling REALLY slows down tests, so don't do it very 
often for SOMETIMES.
@@ -710,24 +762,32 @@ namespace Lucene.Net.Store
 
         internal void AddFileHandle(IDisposable c, string name, Handle handle)
         {
+            lock (locker)
+            {
+                AddFileHandleLocked(c, name, handle);
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private void AddFileHandleLocked(IDisposable c, string name, Handle 
handle)
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(AddFileHandleLocked)} must be called within 
the lock {nameof(locker)}");
+
             //Trace.TraceInformation("Add {0} {1}", c, name);
 
-            lock (this)
+            if (openFiles.TryGetValue(name, out int v))
             {
-                if (openFiles.TryGetValue(name, out int v))
-                {
-                    v++;
-                    //Debug.WriteLine("Add {0} - {1} - {2}", c, name, v);
-                    openFiles[name] = v;
-                }
-                else
-                {
-                    //Debug.WriteLine("Add {0} - {1} - {2}", c, name, 1);
-                    openFiles[name] = 1;
-                }
-
-                openFileHandles[c] = new Exception("unclosed Index" + 
handle.ToString() + ": " + name);
+                v++;
+                //Debug.WriteLine("Add {0} - {1} - {2}", c, name, v);
+                openFiles[name] = v;
             }
+            else
+            {
+                //Debug.WriteLine("Add {0} - {1} - {2}", c, name, 1);
+                openFiles[name] = 1;
+            }
+
+            openFileHandles[c] = new Exception("unclosed Index" + 
handle.ToString() + ": " + name);
         }
 
         private bool failOnOpenInput = true;
@@ -740,14 +800,14 @@ namespace Lucene.Net.Store
 
         public override IndexInput OpenInput(string name, IOContext context)
         {
-            lock (this)
+            lock (locker)
             {
-                MaybeThrowDeterministicException();
+                MaybeThrowDeterministicExceptionLocked();
                 MaybeThrowIOExceptionOnOpen(name);
                 MaybeYield();
                 if (failOnOpenInput)
                 {
-                    MaybeThrowDeterministicException();
+                    MaybeThrowDeterministicExceptionLocked();
                 }
                 if (!LuceneTestCase.SlowFileExists(m_input, name))
                 {
@@ -758,7 +818,7 @@ namespace Lucene.Net.Store
                 // output, except for segments.gen and segments_N
                 if (!allowReadingFilesStillOpenForWrite && 
openFilesForWrite.Contains(name) && !name.StartsWith("segments", 
StringComparison.Ordinal))
                 {
-                    throw WithAdditionalErrorInformation(new 
IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for 
writing"), name, false);
+                    throw WithAdditionalErrorInformationLocked(new 
IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for 
writing"), name, false);
                 }
 
                 IndexInput delegateInput = m_input.OpenInput(name, 
LuceneTestCase.NewIOContext(randomState, context));
@@ -785,7 +845,7 @@ namespace Lucene.Net.Store
                 {
                     ii = new MockIndexInputWrapper(this, name, delegateInput);
                 }
-                AddFileHandle(ii, name, Handle.Input);
+                AddFileHandleLocked(ii, name, Handle.Input);
                 return ii;
             }
         }
@@ -794,11 +854,11 @@ namespace Lucene.Net.Store
         /// Provided for testing purposes.  Use <see cref="GetSizeInBytes()"/> 
instead. </summary>
         public long GetRecomputedSizeInBytes()
         {
-            lock (this)
+            lock (locker)
             {
                 if (!(m_input is RAMDirectory))
                 {
-                    return GetSizeInBytes();
+                    return GetSizeInBytesLocked();
                 }
                 long size = 0;
                 foreach (RAMFile file in 
((RAMDirectory)m_input).m_fileMap.Values)
@@ -818,11 +878,11 @@ namespace Lucene.Net.Store
 
         public long GetRecomputedActualSizeInBytes()
         {
-            lock (this)
+            lock (locker)
             {
                 if (!(m_input is RAMDirectory))
                 {
-                    return GetSizeInBytes();
+                    return GetSizeInBytesLocked();
                 }
                 long size = 0;
                 foreach (RAMFile file in 
((RAMDirectory)m_input).m_fileMap.Values)
@@ -859,7 +919,7 @@ namespace Lucene.Net.Store
 
         protected override void Dispose(bool disposing)
         {
-            lock (this)
+            lock (locker)
             {
                 if (disposing)
                 {
@@ -899,7 +959,7 @@ namespace Lucene.Net.Store
                             {
                                 Console.WriteLine("\nNOTE: 
MockDirectoryWrapper: now crush");
                             }
-                            Crash(); // corrupt any unsynced-files
+                            CrashLocked(); // corrupt any unsynced-files
                             if (LuceneTestCase.Verbose)
                             {
                                 Console.WriteLine("\nNOTE: 
MockDirectoryWrapper: now run CheckIndex");
@@ -1045,9 +1105,20 @@ namespace Lucene.Net.Store
 
         internal virtual void RemoveOpenFile(IDisposable c, string name)
         {
+            lock (locker)
+            {
+                RemoveOpenFileLocked(c, name);
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private void RemoveOpenFileLocked(IDisposable c, string name)
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(RemoveOpenFileLocked)} must be called 
within the lock {nameof(locker)}");
+
             //Trace.TraceInformation("Rem {0} {1}", c, name);
 
-            lock (this)
+            lock (locker)
             {
                 if (openFiles.TryGetValue(name, out int v))
                 {
@@ -1070,18 +1141,18 @@ namespace Lucene.Net.Store
 
         public virtual void RemoveIndexOutput(IndexOutput @out, string name)
         {
-            lock (this)
+            lock (locker)
             {
                 openFilesForWrite.Remove(name);
-                RemoveOpenFile(@out, name);
+                RemoveOpenFileLocked(@out, name);
             }
         }
 
         public virtual void RemoveIndexInput(IndexInput @in, string name)
         {
-            lock (this)
+            lock (locker)
             {
-                RemoveOpenFile(@in, name);
+                RemoveOpenFileLocked(@in, name);
             }
         }
 
@@ -1095,7 +1166,7 @@ namespace Lucene.Net.Store
         /// </summary>
         public virtual void FailOn(Failure fail)
         {
-            lock (this)
+            lock (locker)
             {
                 if (failures == null)
                 {
@@ -1111,21 +1182,29 @@ namespace Lucene.Net.Store
         /// </summary>
         internal virtual void MaybeThrowDeterministicException()
         {
-            lock (this)
+            lock (locker)
+            {
+                MaybeThrowDeterministicExceptionLocked();
+            }
+        }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private void MaybeThrowDeterministicExceptionLocked()
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(MaybeThrowDeterministicExceptionLocked)} 
must be called within the lock {nameof(locker)}");
+
+            if (failures != null)
             {
-                if (failures != null)
+                for (int i = 0; i < failures.Count; i++)
                 {
-                    for (int i = 0; i < failures.Count; i++)
-                    {
-                        failures[i].Eval(this);
-                    }
+                    failures[i].Eval(this);
                 }
             }
         }
 
         public override string[] ListAll()
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
                 return m_input.ListAll();
@@ -1135,7 +1214,7 @@ namespace Lucene.Net.Store
         [Obsolete("this method will be removed in 5.0")]
         public override bool FileExists(string name)
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
                 return m_input.FileExists(name);
@@ -1144,16 +1223,24 @@ namespace Lucene.Net.Store
 
         public override long FileLength(string name)
         {
-            lock (this)
+            lock (locker)
             {
-                MaybeYield();
-                return m_input.FileLength(name);
+                return FileLengthLocked(name);
             }
         }
+        // LUCENENET specific for avoiding deadlocks and recursive locks
+        private long FileLengthLocked(string name)
+        {
+            if (!Monitor.IsEntered(locker))
+                throw new 
InvalidOperationException($"{nameof(FileLengthLocked)} must be called within 
the lock {nameof(locker)}");
+
+            MaybeYield();
+            return m_input.FileLength(name);
+        }
 
         public override Lock MakeLock(string name)
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
                 return LockFactory.MakeLock(name);
@@ -1162,7 +1249,7 @@ namespace Lucene.Net.Store
 
         public override void ClearLock(string name)
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
                 LockFactory.ClearLock(name);
@@ -1171,7 +1258,7 @@ namespace Lucene.Net.Store
 
         public override void SetLockFactory(LockFactory lockFactory)
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
                 // sneaky: we must pass the original this way to the dir, 
because
@@ -1186,7 +1273,7 @@ namespace Lucene.Net.Store
         {
             get
             {
-                lock (this)
+                lock (locker)
                 {
                     MaybeYield();
                     if (wrapLockFactory)
@@ -1203,7 +1290,7 @@ namespace Lucene.Net.Store
 
         public override string GetLockID()
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
                 return m_input.GetLockID();
@@ -1212,7 +1299,7 @@ namespace Lucene.Net.Store
 
         public override void Copy(Directory to, string src, string dest, 
IOContext context)
         {
-            lock (this)
+            lock (locker)
             {
                 MaybeYield();
                 // randomize the IOContext here?
diff --git a/src/Lucene.Net.Tests._E-I/FindFirstFailingSeedAttribute.cs 
b/src/Lucene.Net.Tests._E-I/FindFirstFailingSeedAttribute.cs
new file mode 100644
index 0000000..a323a6d
--- /dev/null
+++ b/src/Lucene.Net.Tests._E-I/FindFirstFailingSeedAttribute.cs
@@ -0,0 +1,97 @@
+using NUnit.Framework;
+using NUnit.Framework.Interfaces;
+using NUnit.Framework.Internal;
+using NUnit.Framework.Internal.Commands;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+
+namespace Lucene.Net
+{
+    /// <summary>
+    /// Provides a way to keep running the test in NUnit with a different test 
seed
+    /// </summary>
+    /// <remarks>
+    /// see https://github.com/nunit/nunit/issues/1461#issuecomment-429580661
+    /// </remarks>
+    [AttributeUsage(AttributeTargets.Method)]
+    public sealed class FindFirstFailingSeedAttribute : Attribute, 
IWrapSetUpTearDown
+    {
+        public int StartingSeed { get; set; }
+        public int TimeoutMilliseconds { get; set; } = Timeout.Infinite;
+
+        public TestCommand Wrap(TestCommand command)
+        {
+            return new FindFirstFailingSeedCommand(command, StartingSeed, 
TimeoutMilliseconds);
+        }
+
+        private sealed class FindFirstFailingSeedCommand : 
DelegatingTestCommand
+        {
+            private readonly int startingSeed;
+            private readonly int timeoutMilliseconds;
+
+            public FindFirstFailingSeedCommand(TestCommand innerCommand, int 
startingSeed, int timeoutMilliseconds) : base(innerCommand)
+            {
+                this.startingSeed = startingSeed;
+                this.timeoutMilliseconds = timeoutMilliseconds;
+            }
+
+            public override TestResult Execute(TestExecutionContext context)
+            {
+                var stopwatch = timeoutMilliseconds == Timeout.Infinite ? null 
: Stopwatch.StartNew();
+
+                for (var seed = startingSeed; ;)
+                {
+                    ResetRandomSeed(context, seed);
+                    context.CurrentResult = 
context.CurrentTest.MakeTestResult();
+
+                    try
+                    {
+                        context.CurrentResult = innerCommand.Execute(context);
+                    }
+                    catch (Exception ex)
+                    {
+                        context.CurrentResult.RecordException(ex);
+                    }
+
+                    if (context.CurrentTest.Seed != seed)
+                        throw new 
InvalidOperationException($"{nameof(FindFirstFailingSeedAttribute)} cannot be 
used together with an attribute or test that changes the seed.");
+
+                    TestContext.WriteLine($"Random seed: {seed}");
+
+                    if (context.CurrentResult.ResultState.Status == 
TestStatus.Failed)
+                    {                        
+                        break;
+                    }
+
+                    seed++;
+                    if (seed == startingSeed)
+                    {
+                        TestContext.WriteLine("Tried every seed without 
producing a failure.");
+                        break;
+                    }
+
+                    if (stopwatch != null && stopwatch.ElapsedMilliseconds > 
timeoutMilliseconds)
+                    {
+                        TestContext.WriteLine($"Timed out after seeds 
{startingSeed}–{seed} did not produce a failure.");
+                        break;
+                    }
+                }
+
+                return context.CurrentResult;
+            }
+
+            private static void ResetRandomSeed(TestExecutionContext context, 
int seed)
+            {
+                context.CurrentTest.Seed = seed;
+
+                typeof(TestExecutionContext)
+                    .GetField("_randomGenerator", BindingFlags.NonPublic | 
BindingFlags.Instance | BindingFlags.DeclaredOnly)
+                    .SetValue(context, null);
+            }
+        }
+    }
+}
diff --git a/src/Lucene.Net.Tests/Index/TestAddIndexes.cs 
b/src/Lucene.Net.Tests/Index/TestAddIndexes.cs
index 62dad40..a0eaa98 100644
--- a/src/Lucene.Net.Tests/Index/TestAddIndexes.cs
+++ b/src/Lucene.Net.Tests/Index/TestAddIndexes.cs
@@ -6,6 +6,7 @@ using Lucene.Net.Support;
 using NUnit.Framework;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Threading;
@@ -983,6 +984,19 @@ namespace Lucene.Net.Index
             }
         }
 
+        [Explicit("Used for debugging the long running test")]
+        [FindFirstFailingSeed(TimeoutMilliseconds = 240000)]
+        [Test]
+        public virtual void TestAddIndexesWithCloseNoWait_Repeated()
+        {
+            TestContext.WriteLine("Starting");
+            var watch = Stopwatch.StartNew();
+            TestAddIndexesWithCloseNoWait();
+            if (watch.ElapsedMilliseconds > 60000)
+                throw new InvalidOperationException($"Took too long 
{watch.ElapsedMilliseconds}");
+            TestContext.WriteLine($"Completed {watch.ElapsedMilliseconds}");
+        }
+
         // LUCENE-1335: test simultaneous addIndexes & close
         [Test]
         [Slow]

Reply via email to