blob: a7f51d30bb0483e66403c17e921c59471032c6cd [file] [log] [blame]
using J2N.Runtime.CompilerServices;
using J2N.Threading.Atomic;
using Lucene.Net.Diagnostics;
using Lucene.Net.Index;
using Lucene.Net.Index.Extensions;
using Lucene.Net.Support;
using Lucene.Net.Util;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using AssertionError = Lucene.Net.Diagnostics.AssertionException;
using Console = Lucene.Net.Util.SystemConsole;
using JCG = J2N.Collections.Generic;
#if FEATURE_SERIALIZABLE_EXCEPTIONS
using System.Runtime.Serialization;
#endif
namespace Lucene.Net.Store
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <summary>
/// Enum for controlling hard disk throttling.
/// Set via <see cref="MockDirectoryWrapper.Throttling"/>
/// <para/>
/// WARNING: can make tests very slow.
/// </summary>
public enum Throttling
{
/// <summary>
/// always emulate a slow hard disk. could be very slow! </summary>
ALWAYS,
/// <summary>
/// sometimes (2% of the time) emulate a slow hard disk. </summary>
SOMETIMES,
/// <summary>
/// never throttle output </summary>
NEVER
}
/// <summary>
/// This is a Directory Wrapper that adds methods
/// intended to be used only by unit tests.
/// It also adds a number of features useful for testing:
/// <list type="bullet">
/// <item>
/// <description>
/// Instances created by <see cref="LuceneTestCase.NewDirectory()"/> are tracked
/// to ensure they are disposed by the test.
/// </description>
/// </item>
/// <item>
/// <description>
/// When a <see cref="MockDirectoryWrapper"/> is disposed, it will throw an exception if
/// it has any open files against it (with a stacktrace indicating where
/// they were opened from).
/// </description>
/// </item>
/// <item>
/// <description>
/// When a <see cref="MockDirectoryWrapper"/> is disposed, it runs <see cref="Index.CheckIndex"/> to test if
/// the index was corrupted.
/// </description>
/// </item>
/// <item>
/// <description>
/// <see cref="MockDirectoryWrapper"/> simulates some "features" of Windows, such as
/// refusing to write/delete to open files.
/// </description>
/// </item>
/// </list>
/// </summary>
public class MockDirectoryWrapper : BaseDirectoryWrapper
{
internal long maxSize;
// Max actual bytes used. this is set by MockRAMOutputStream:
internal long maxUsedSize;
internal double randomIOExceptionRate;
internal double randomIOExceptionRateOnOpen;
internal Random randomState;
internal bool noDeleteOpenFile = true;
internal bool assertNoDeleteOpenFile = false;
internal bool preventDoubleWrite = true;
internal bool trackDiskUsage = false;
internal bool wrapLockFactory = true;
internal bool allowRandomFileNotFoundException = true;
internal bool allowReadingFilesStillOpenForWrite = false;
private ISet<string> unSyncedFiles;
private ISet<string> createdFiles;
private ISet<string> openFilesForWrite = new JCG.HashSet<string>(StringComparer.Ordinal);
internal ISet<string> openLocks = new ConcurrentHashSet<string>(StringComparer.Ordinal);
internal volatile bool crashed;
private readonly ThrottledIndexOutput throttledOutput; // LUCENENET: marked readonly
private Throttling throttling = Throttling.SOMETIMES;
protected LockFactory m_lockFactory;
internal readonly AtomicInt32 inputCloneCount = new AtomicInt32();
// use this for tracking files for crash.
// additionally: provides debugging information in case you leave one open
private readonly ConcurrentDictionary<IDisposable, Exception> openFileHandles = new ConcurrentDictionary<IDisposable, Exception>(IdentityEqualityComparer<IDisposable>.Default);
// NOTE: we cannot initialize the Map here due to the
// order in which our constructor actually does this
// member initialization vs when it calls super. It seems
// like super is called, then our members are initialized:
private IDictionary<string, int> openFiles;
// Only tracked if noDeleteOpenFile is true: if an attempt
// is made to delete an open file, we enroll it here.
private ISet<string> openFilesDeleted;
private void Init()
{
lock (this)
{
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);
}
}
}
public MockDirectoryWrapper(Random random, Directory @delegate)
: base(@delegate)
{
// must make a private random since our methods are
// called from different threads; else test failures may
// not be reproducible from the original seed
this.randomState = new Random(random.Next());
this.throttledOutput = new ThrottledIndexOutput(ThrottledIndexOutput.MBitsToBytes(40 + randomState.Next(10)), 5 + randomState.Next(5), null);
// force wrapping of lockfactory
this.m_lockFactory = new MockLockFactoryWrapper(this, @delegate.LockFactory);
Init();
}
public virtual int InputCloneCount => inputCloneCount;
public virtual bool TrackDiskUsage
{
get => trackDiskUsage; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => trackDiskUsage = value;
}
/// <summary>
/// If set to true, we throw an <see cref="IOException"/> if the same
/// file is opened by <see cref="CreateOutput(string, IOContext)"/>, ever.
/// </summary>
public virtual bool PreventDoubleWrite
{
get => preventDoubleWrite; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => preventDoubleWrite = value;
}
/// <summary>
/// If set to true (the default), when we throw random
/// <see cref="IOException"/> on <see cref="OpenInput(string, IOContext)"/> or
/// <see cref="CreateOutput(string, IOContext)"/>, we may
/// sometimes throw <see cref="FileNotFoundException"/>.
/// </summary>
public virtual bool AllowRandomFileNotFoundException
{
get => allowRandomFileNotFoundException; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => allowRandomFileNotFoundException = value;
}
/// <summary>
/// If set to true, you can open an inputstream on a file
/// that is still open for writes.
/// </summary>
public virtual bool AllowReadingFilesStillOpenForWrite
{
get => allowRandomFileNotFoundException; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => allowReadingFilesStillOpenForWrite = value;
}
// LUCENENET specific - de-nested Throttling enum
public virtual Throttling Throttling
{
get => throttling; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => throttling = value;
}
/// <summary>
/// Returns true if <see cref="FilterDirectory.m_input"/> must sync its files.
/// Currently, only <see cref="NRTCachingDirectory"/> requires sync'ing its files
/// because otherwise they are cached in an internal <see cref="RAMDirectory"/>. If
/// other directories require that too, they should be added to this method.
/// </summary>
private bool MustSync()
{
Directory @delegate = m_input;
while (@delegate is FilterDirectory filterDirectory)
{
@delegate = filterDirectory.Delegate;
}
return @delegate is NRTCachingDirectory;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public override void Sync(ICollection<string> names)
{
lock (this)
{
MaybeYield();
MaybeThrowDeterministicException();
if (crashed)
{
throw new IOException("cannot sync after crash");
}
// don't wear out our hardware so much in tests.
if (LuceneTestCase.Rarely(randomState) || MustSync())
{
foreach (string name in names)
{
// randomly fail with IOE on any file
MaybeThrowIOException(name);
m_input.Sync(new[] { name });
unSyncedFiles.Remove(name);
}
}
else
{
unSyncedFiles.RemoveAll(names);
}
}
}
public long GetSizeInBytes()
{
lock (this)
{
if (m_input is RAMDirectory ramDirectory)
{
return ramDirectory.GetSizeInBytes();
}
else
{
// hack
long size = 0;
foreach (string file in m_input.ListAll())
{
size += m_input.FileLength(file);
}
return size;
}
}
}
/// <summary>
/// Simulates a crash of OS or machine by overwriting
/// unsynced files.
/// </summary>
public virtual void Crash()
{
lock (this)
{
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)
{
try
{
f.Dispose();
}
#pragma warning disable 168
catch (Exception ignored)
#pragma warning restore 168
{
//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;
if (damage == 0)
{
action = "deleted";
DeleteFile(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));
while (upto < length)
{
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:
// First, make temp file and copy only half this
// file over:
string tempFileName;
while (true)
{
tempFileName = "" + randomState.Next();
if (!LuceneTestCase.SlowFileExists(m_input, tempFileName))
{
break;
}
}
using (IndexOutput tempOut = m_input.CreateOutput(tempFileName, LuceneTestCase.NewIOContext(randomState)))
using (IndexInput ii = m_input.OpenInput(name, LuceneTestCase.NewIOContext(randomState)))
{
tempOut.CopyBytes(ii, ii.Length / 2);
}
// Delete original and copy bytes back:
DeleteFile(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)
{
// 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));
@out.Length = 0;
}
if (LuceneTestCase.Verbose)
{
Console.WriteLine("MockDirectoryWrapper: " + action + " unsynced file: " + name);
}
}
}
}
public virtual void ClearCrash()
{
lock (this)
{
crashed = false;
openLocks.Clear();
}
}
public virtual long MaxSizeInBytes
{
get => maxSize;
set => maxSize = value;
}
/// <summary>
/// Returns the peek actual storage used (bytes) in this
/// directory.
/// </summary>
public virtual long MaxUsedSizeInBytes => maxUsedSize;
public virtual void ResetMaxUsedSizeInBytes()
{
this.maxUsedSize = GetRecomputedActualSizeInBytes();
}
/// <summary>
/// Emulate Windows whereby deleting an open file is not
/// allowed (raise <see cref="IOException"/>).
/// </summary>
public virtual bool NoDeleteOpenFile
{
get => noDeleteOpenFile;
set => noDeleteOpenFile = value;
}
/// <summary>
/// Trip a test assert if there is an attempt
/// to delete an open file.
/// </summary>
public virtual bool AssertNoDeleteOpenFile
{
get => assertNoDeleteOpenFile;
set => assertNoDeleteOpenFile = value;
}
/// <summary>
/// If 0.0, no exceptions will be thrown. Else this should
/// be a double 0.0 - 1.0. We will randomly throw an
/// <see cref="IOException"/> on the first write to a <see cref="Stream"/> based
/// on this probability.
/// </summary>
public virtual double RandomIOExceptionRate
{
get => randomIOExceptionRate;
set => randomIOExceptionRate = value;
}
/// <summary>
/// If 0.0, no exceptions will be thrown during <see cref="OpenInput(string, IOContext)"/>
/// and <see cref="CreateOutput(string, IOContext)"/>. Else this should
/// be a double 0.0 - 1.0 and we will randomly throw an
/// <see cref="IOException"/> in <see cref="OpenInput(string, IOContext)"/> and <see cref="CreateOutput(string, IOContext)"/> with
/// this probability.
/// </summary>
public virtual double RandomIOExceptionRateOnOpen
{
get => randomIOExceptionRateOnOpen;
set => randomIOExceptionRateOnOpen = value;
}
internal virtual void MaybeThrowIOException(string message)
{
if (randomState.NextDouble() < randomIOExceptionRate)
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine(Thread.CurrentThread.Name + ": MockDirectoryWrapper: now throw random exception" + (message == null ? "" : " (" + message + ")"));
}
throw new IOException("a random IOException" + (message == null ? "" : " (" + message + ")"));
}
}
internal virtual void MaybeThrowIOExceptionOnOpen(string name)
{
if (randomState.NextDouble() < randomIOExceptionRateOnOpen)
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine(Thread.CurrentThread.Name + ": MockDirectoryWrapper: now throw random exception during open file=" + name);
}
if (allowRandomFileNotFoundException == false || randomState.NextBoolean())
{
throw new IOException("a random IOException (" + name + ")");
}
else
{
throw randomState.NextBoolean() ? (IOException)new FileNotFoundException("a random IOException (" + name + ")") : new DirectoryNotFoundException("a random IOException (" + name + ")");
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public override void DeleteFile(string name)
{
lock (this)
{
MaybeYield();
DeleteFile(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)
{
foreach (var ent in openFileHandles)
{
if (input && ent.Key is MockIndexInputWrapper mockIndexInputWrapper && mockIndexInputWrapper.name.Equals(name, StringComparison.Ordinal))
{
t = CreateException(t, ent.Value);
break;
}
else if (!input && ent.Key is MockIndexOutputWrapper mockIndexOutputWrapper && mockIndexOutputWrapper.name.Equals(name, StringComparison.Ordinal))
{
t = CreateException(t, ent.Value);
break;
}
}
return t;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Exception CreateException(Exception exception, Exception innerException) // LUCENENET: CA1822: Mark members as static
{
return (Exception)Activator.CreateInstance(exception.GetType(), exception.Message, innerException);
}
private void MaybeYield()
{
if (randomState.NextBoolean())
{
#if !FEATURE_THREAD_YIELD
Thread.Sleep(0);
#else
Thread.Yield();
#endif
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void DeleteFile(string name, bool forced)
{
lock (this)
{
MaybeYield();
MaybeThrowDeterministicException();
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))
{
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);
}
}
else
{
openFilesDeleted.Remove(name);
}
}
m_input.DeleteFile(name);
}
}
public virtual ICollection<string> GetOpenDeletedFiles()
{
lock (this)
{
return new JCG.HashSet<string>(openFilesDeleted, StringComparer.Ordinal);
}
}
private bool failOnCreateOutput = true;
public virtual bool FailOnCreateOutput
{
get => failOnCreateOutput; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => failOnCreateOutput = value;
}
public override IndexOutput CreateOutput(string name, IOContext context)
{
lock (this)
{
MaybeThrowDeterministicException();
MaybeThrowIOExceptionOnOpen(name);
MaybeYield();
if (failOnCreateOutput)
{
MaybeThrowDeterministicException();
}
if (crashed)
{
throw new IOException("cannot createOutput after crash");
}
Init();
lock (this)
{
if (preventDoubleWrite && createdFiles.Contains(name) && !name.Equals("segments.gen", StringComparison.Ordinal))
{
throw new IOException("file \"" + name + "\" was already written to");
}
}
if ((noDeleteOpenFile || assertNoDeleteOpenFile) && openFiles.ContainsKey(name))
{
if (!assertNoDeleteOpenFile)
{
throw new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot overwrite");
}
else
{
throw new AssertionError("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot overwrite");
}
}
if (crashed)
{
throw new IOException("cannot createOutput after crash");
}
unSyncedFiles.Add(name);
createdFiles.Add(name);
if (m_input is RAMDirectory ramdir)
{
RAMFile file = new RAMFile(ramdir);
ramdir.m_fileMap.TryGetValue(name, out RAMFile existing);
// Enforce write once:
if (existing != null && !name.Equals("segments.gen", StringComparison.Ordinal) && preventDoubleWrite)
{
throw new IOException("file " + name + " already exists");
}
else
{
if (existing != null)
{
ramdir.m_sizeInBytes.AddAndGet(-existing.GetSizeInBytes()); // LUCENENET: GetAndAdd in Lucene, but we are not using the value
existing.directory = null;
}
ramdir.m_fileMap[name] = file;
}
}
//System.out.println(Thread.currentThread().getName() + ": MDW: create " + name);
IndexOutput delegateOutput = m_input.CreateOutput(name, LuceneTestCase.NewIOContext(randomState, context));
if (randomState.Next(10) == 0)
{
// once in a while wrap the IO in a Buffered IO with random buffer sizes
delegateOutput = new BufferedIndexOutputWrapper(1 + randomState.Next(BufferedIndexOutput.DEFAULT_BUFFER_SIZE), delegateOutput);
}
IndexOutput io = new MockIndexOutputWrapper(this, delegateOutput, name);
AddFileHandle(io, name, Handle.Output);
openFilesForWrite.Add(name);
// throttling REALLY slows down tests, so don't do it very often for SOMETIMES.
if (throttling == Throttling.ALWAYS || (throttling == Throttling.SOMETIMES && randomState.Next(50) == 0) && !(m_input is RateLimitedDirectoryWrapper))
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine("MockDirectoryWrapper: throttling indexOutput (" + name + ")");
}
return throttledOutput.NewFromDelegate(io);
}
else
{
return io;
}
}
}
internal enum Handle
{
Input,
Output,
Slice
}
internal void AddFileHandle(IDisposable c, string name, Handle handle)
{
//Trace.TraceInformation("Add {0} {1}", c, name);
lock (this)
{
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);
}
}
private bool failOnOpenInput = true;
public virtual bool FailOnOpenInput
{
get => failOnOpenInput; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => failOnOpenInput = value;
}
public override IndexInput OpenInput(string name, IOContext context)
{
lock (this)
{
MaybeThrowDeterministicException();
MaybeThrowIOExceptionOnOpen(name);
MaybeYield();
if (failOnOpenInput)
{
MaybeThrowDeterministicException();
}
if (!LuceneTestCase.SlowFileExists(m_input, name))
{
throw randomState.NextBoolean() ? (IOException)new FileNotFoundException(name + " in dir=" + m_input) : new DirectoryNotFoundException(name + " in dir=" + m_input);
}
// cannot open a file for input if it's still open for
// 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);
}
IndexInput delegateInput = m_input.OpenInput(name, LuceneTestCase.NewIOContext(randomState, context));
IndexInput ii;
int randomInt = randomState.Next(500);
if (randomInt == 0)
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine("MockDirectoryWrapper: using SlowClosingMockIndexInputWrapper for file " + name);
}
ii = new SlowClosingMockIndexInputWrapper(this, name, delegateInput);
}
else if (randomInt == 1)
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine("MockDirectoryWrapper: using SlowOpeningMockIndexInputWrapper for file " + name);
}
ii = new SlowOpeningMockIndexInputWrapper(this, name, delegateInput);
}
else
{
ii = new MockIndexInputWrapper(this, name, delegateInput);
}
AddFileHandle(ii, name, Handle.Input);
return ii;
}
}
/// <summary>
/// Provided for testing purposes. Use <see cref="GetSizeInBytes()"/> instead. </summary>
public long GetRecomputedSizeInBytes()
{
lock (this)
{
if (!(m_input is RAMDirectory))
{
return GetSizeInBytes();
}
long size = 0;
foreach (RAMFile file in ((RAMDirectory)m_input).m_fileMap.Values)
{
size += file.GetSizeInBytes();
}
return size;
}
}
/// <summary>
/// Like <see cref="GetRecomputedSizeInBytes()"/>, but, uses actual file
/// lengths rather than buffer allocations (which are
/// quantized up to nearest
/// <see cref="RAMOutputStream.BUFFER_SIZE"/> (now 1024) bytes.
/// </summary>
public long GetRecomputedActualSizeInBytes()
{
lock (this)
{
if (!(m_input is RAMDirectory))
{
return GetSizeInBytes();
}
long size = 0;
foreach (RAMFile file in ((RAMDirectory)m_input).m_fileMap.Values)
{
size += file.Length;
}
return size;
}
}
// NOTE: this is off by default; see LUCENE-5574
private bool assertNoUnreferencedFilesOnClose;
public virtual bool AssertNoUnreferencedFilesOnClose // LUCENENET TODO: Rename AssertNoUnreferencedFilesOnDispose ?
{
get => assertNoUnreferencedFilesOnClose; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => assertNoUnreferencedFilesOnClose = value;
}
/// <summary>
/// Set to false if you want to return the pure lockfactory
/// and not wrap it with <see cref="MockLockFactoryWrapper"/>.
/// <para/>
/// Be careful if you turn this off: <see cref="MockDirectoryWrapper"/> might
/// no longer be able to detect if you forget to close an <see cref="IndexWriter"/>,
/// and spit out horribly scary confusing exceptions instead of
/// simply telling you that.
/// </summary>
public virtual bool WrapLockFactory
{
get => wrapLockFactory; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => wrapLockFactory = value;
}
protected override void Dispose(bool disposing)
{
lock (this)
{
if (disposing)
{
// files that we tried to delete, but couldn't because readers were open.
// all that matters is that we tried! (they will eventually go away)
ISet<string> pendingDeletions = new JCG.HashSet<string>(openFilesDeleted, StringComparer.Ordinal);
MaybeYield();
if (openFiles == null)
{
openFiles = new Dictionary<string, int>(StringComparer.Ordinal);
openFilesDeleted = new JCG.HashSet<string>(StringComparer.Ordinal);
}
if (openFiles.Count > 0)
{
// print the first one as its very verbose otherwise
Exception cause = openFileHandles.Values.FirstOrDefault();
// RuntimeException instead ofIOException because
// super() does not throw IOException currently:
throw new Exception("MockDirectoryWrapper: cannot close: there are still open files: "
+ Collections.ToString(openFiles), cause);
}
if (openLocks.Count > 0)
{
throw new Exception("MockDirectoryWrapper: cannot close: there are still open locks: "
+ Collections.ToString(openLocks));
}
IsOpen = false;
if (CheckIndexOnDispose)
{
randomIOExceptionRate = 0.0;
randomIOExceptionRateOnOpen = 0.0;
if (DirectoryReader.IndexExists(this))
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine("\nNOTE: MockDirectoryWrapper: now crush");
}
Crash(); // corrupt any unsynced-files
if (LuceneTestCase.Verbose)
{
Console.WriteLine("\nNOTE: MockDirectoryWrapper: now run CheckIndex");
}
TestUtil.CheckIndex(this, CrossCheckTermVectorsOnDispose);
// TODO: factor this out / share w/ TestIW.assertNoUnreferencedFiles
if (assertNoUnreferencedFilesOnClose)
{
// now look for unreferenced files: discount ones that we tried to delete but could not
ISet<string> allFiles = new JCG.HashSet<string>(ListAll());
allFiles.ExceptWith(pendingDeletions);
string[] startFiles = allFiles.ToArray(/*new string[0]*/);
IndexWriterConfig iwc = new IndexWriterConfig(LuceneTestCase.TEST_VERSION_CURRENT, null);
iwc.SetIndexDeletionPolicy(NoDeletionPolicy.INSTANCE);
new IndexWriter(m_input, iwc).Rollback();
string[] endFiles = m_input.ListAll();
ISet<string> startSet = new JCG.SortedSet<string>(startFiles, StringComparer.Ordinal);
ISet<string> endSet = new JCG.SortedSet<string>(endFiles, StringComparer.Ordinal);
if (pendingDeletions.Contains("segments.gen") && endSet.Contains("segments.gen"))
{
// this is possible if we hit an exception while writing segments.gen, we try to delete it
// and it ends out in pendingDeletions (but IFD wont remove this).
startSet.Add("segments.gen");
if (LuceneTestCase.Verbose)
{
Console.WriteLine("MDW: Unreferenced check: Ignoring segments.gen that we could not delete.");
}
}
// its possible we cannot delete the segments_N on windows if someone has it open and
// maybe other files too, depending on timing. normally someone on windows wouldnt have
// an issue (IFD would nuke this stuff eventually), but we pass NoDeletionPolicy...
foreach (string file in pendingDeletions)
{
if (file.StartsWith("segments", StringComparison.Ordinal) && !file.Equals("segments.gen", StringComparison.Ordinal) && endSet.Contains(file))
{
startSet.Add(file);
if (LuceneTestCase.Verbose)
{
Console.WriteLine("MDW: Unreferenced check: Ignoring segments file: " + file + " that we could not delete.");
}
SegmentInfos sis = new SegmentInfos();
try
{
sis.Read(m_input, file);
}
#pragma warning disable 168
catch (IOException ioe)
#pragma warning restore 168
{
// OK: likely some of the .si files were deleted
}
try
{
ISet<string> ghosts = new JCG.HashSet<string>(sis.GetFiles(m_input, false));
foreach (string s in ghosts)
{
if (endSet.Contains(s) && !startSet.Contains(s))
{
if (Debugging.AssertsEnabled) Debugging.Assert(pendingDeletions.Contains(s));
if (LuceneTestCase.Verbose)
{
Console.WriteLine("MDW: Unreferenced check: Ignoring referenced file: " + s + " " +
"from " + file + " that we could not delete.");
}
startSet.Add(s);
}
}
}
catch (Exception t)
{
Console.Error.WriteLine("ERROR processing leftover segments file " + file + ":");
Console.WriteLine(t.ToString());
}
}
}
startFiles = startSet.ToArray(/*new string[0]*/);
endFiles = endSet.ToArray(/*new string[0]*/);
if (!Arrays.Equals(startFiles, endFiles))
{
IList<string> removed = new List<string>();
foreach (string fileName in startFiles)
{
if (!endSet.Contains(fileName))
{
removed.Add(fileName);
}
}
IList<string> added = new List<string>();
foreach (string fileName in endFiles)
{
if (!startSet.Contains(fileName))
{
added.Add(fileName);
}
}
string extras;
if (removed.Count != 0)
{
extras = "\n\nThese files were removed: " + Collections.ToString(removed);
}
else
{
extras = "";
}
if (added.Count != 0)
{
extras += "\n\nThese files were added (waaaaaaaaaat!): " + Collections.ToString(added);
}
if (pendingDeletions.Count != 0)
{
extras += "\n\nThese files we had previously tried to delete, but couldn't: " + pendingDeletions;
}
if (Debugging.AssertsEnabled) Debugging.Assert(false, "unreferenced files: before delete:\n {0}\n after delete:\n {1}{2}", startFiles, endFiles, extras);
}
DirectoryReader ir1 = DirectoryReader.Open(this);
int numDocs1 = ir1.NumDocs;
ir1.Dispose();
(new IndexWriter(this, new IndexWriterConfig(LuceneTestCase.TEST_VERSION_CURRENT, null))).Dispose();
DirectoryReader ir2 = DirectoryReader.Open(this);
int numDocs2 = ir2.NumDocs;
ir2.Dispose();
if (Debugging.AssertsEnabled) Debugging.Assert(numDocs1 == numDocs2,"numDocs changed after opening/closing IW: before={0} after={1}", numDocs1, numDocs2);
}
}
}
m_input.Dispose(); // LUCENENET TODO: using blocks in this entire class
throttledOutput.Dispose(); // LUCENENET specific
}
}
}
internal virtual void RemoveOpenFile(IDisposable c, string name)
{
//Trace.TraceInformation("Rem {0} {1}", c, name);
lock (this)
{
if (openFiles.TryGetValue(name, out int v))
{
if (v == 1)
{
//Debug.WriteLine("RemoveOpenFile OpenFiles.Remove {0} - {1}", c, name);
openFiles.Remove(name);
}
else
{
v--;
openFiles[name] = v;
//Debug.WriteLine("RemoveOpenFile OpenFiles DECREMENT {0} - {1} - {2}", c, name, v);
}
}
openFileHandles.TryRemove(c, out Exception _);
}
}
public virtual void RemoveIndexOutput(IndexOutput @out, string name)
{
lock (this)
{
openFilesForWrite.Remove(name);
RemoveOpenFile(@out, name);
}
}
public virtual void RemoveIndexInput(IndexInput @in, string name)
{
lock (this)
{
RemoveOpenFile(@in, name);
}
}
// LUCENENET specific - de-nested Failure
internal List<Failure> failures;
/// <summary>
/// Add a <see cref="Failure"/> object to the list of objects to be evaluated
/// at every potential failure point.
/// </summary>
public virtual void FailOn(Failure fail)
{
lock (this)
{
if (failures == null)
{
failures = new List<Failure>();
}
failures.Add(fail);
}
}
/// <summary>
/// Iterate through the failures list, giving each object a
/// chance to throw an <see cref="IOException"/>.
/// </summary>
internal virtual void MaybeThrowDeterministicException()
{
lock (this)
{
if (failures != null)
{
for (int i = 0; i < failures.Count; i++)
{
failures[i].Eval(this);
}
}
}
}
public override string[] ListAll()
{
lock (this)
{
MaybeYield();
return m_input.ListAll();
}
}
[Obsolete("this method will be removed in 5.0")]
public override bool FileExists(string name)
{
lock (this)
{
MaybeYield();
return m_input.FileExists(name);
}
}
public override long FileLength(string name)
{
lock (this)
{
MaybeYield();
return m_input.FileLength(name);
}
}
public override Lock MakeLock(string name)
{
lock (this)
{
MaybeYield();
return LockFactory.MakeLock(name);
}
}
public override void ClearLock(string name)
{
lock (this)
{
MaybeYield();
LockFactory.ClearLock(name);
}
}
public override void SetLockFactory(LockFactory lockFactory)
{
lock (this)
{
MaybeYield();
// sneaky: we must pass the original this way to the dir, because
// some impls (e.g. FSDir) do instanceof here.
m_input.SetLockFactory(lockFactory);
// now set our wrapped factory here
this.m_lockFactory = new MockLockFactoryWrapper(this, lockFactory);
}
}
public override LockFactory LockFactory
{
get
{
lock (this)
{
MaybeYield();
if (wrapLockFactory)
{
return m_lockFactory;
}
else
{
return m_input.LockFactory;
}
}
}
}
public override string GetLockID()
{
lock (this)
{
MaybeYield();
return m_input.GetLockID();
}
}
public override void Copy(Directory to, string src, string dest, IOContext context)
{
lock (this)
{
MaybeYield();
// randomize the IOContext here?
m_input.Copy(to, src, dest, context);
}
}
public override IndexInputSlicer CreateSlicer(string name, IOContext context)
{
MaybeYield();
if (!LuceneTestCase.SlowFileExists(m_input, name))
{
throw randomState.NextBoolean() ? (IOException)new FileNotFoundException(name) : new DirectoryNotFoundException(name);
}
// cannot open a file for input if it's still open for
// output, except for segments.gen and segments_N
if (openFilesForWrite.Contains(name) && !name.StartsWith("segments", StringComparison.Ordinal))
{
throw WithAdditionalErrorInformation(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for writing"), name, false);
}
IndexInputSlicer delegateHandle = m_input.CreateSlicer(name, context);
IndexInputSlicer handle = new IndexInputSlicerAnonymousInnerClassHelper(this, name, delegateHandle);
AddFileHandle(handle, name, Handle.Slice);
return handle;
}
private class IndexInputSlicerAnonymousInnerClassHelper : IndexInputSlicer
{
private readonly MockDirectoryWrapper outerInstance;
private readonly string name;
private readonly IndexInputSlicer delegateHandle;
public IndexInputSlicerAnonymousInnerClassHelper(MockDirectoryWrapper outerInstance, string name, IndexInputSlicer delegateHandle)
{
this.outerInstance = outerInstance;
this.name = name;
this.delegateHandle = delegateHandle;
}
private int disposed = 0;
protected override void Dispose(bool disposing)
{
if (0 == Interlocked.CompareExchange(ref this.disposed, 1, 0))
{
if (disposing)
{
delegateHandle.Dispose();
outerInstance.RemoveOpenFile(this, name);
}
}
}
public override IndexInput OpenSlice(string sliceDescription, long offset, long length)
{
outerInstance.MaybeYield();
IndexInput ii = new MockIndexInputWrapper(outerInstance, name, delegateHandle.OpenSlice(sliceDescription, offset, length));
outerInstance.AddFileHandle(ii, name, Handle.Input);
return ii;
}
[Obsolete("Only for reading CFS files from 3.x indexes.")]
public override IndexInput OpenFullSlice()
{
outerInstance.MaybeYield();
IndexInput ii = new MockIndexInputWrapper(outerInstance, name, delegateHandle.OpenFullSlice());
outerInstance.AddFileHandle(ii, name, Handle.Input);
return ii;
}
}
internal sealed class BufferedIndexOutputWrapper : BufferedIndexOutput
{
private readonly IndexOutput io;
public BufferedIndexOutputWrapper(int bufferSize, IndexOutput io)
: base(bufferSize)
{
this.io = io;
}
public override long Length => io.Length;
protected internal override void FlushBuffer(byte[] b, int offset, int len)
{
io.WriteBytes(b, offset, len);
}
[Obsolete("(4.1) this method will be removed in Lucene 5.0")]
public override void Seek(long pos)
{
Flush();
io.Seek(pos);
}
public override void Flush()
{
try
{
base.Flush();
}
finally
{
io.Flush();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
try
{
base.Dispose(disposing);
}
finally
{
io.Dispose();
}
}
}
}
// LUCENENET specific - de-nested FakeIOException
}
/// <summary>
/// Objects that represent fail-able conditions. Objects of a derived
/// class are created and registered with the mock directory. After
/// register, each object will be invoked once for each first write
/// of a file, giving the object a chance to throw an <see cref="IOException"/>.
/// </summary>
public class Failure
{
/// <summary>
/// Eval is called on the first write of every new file.
/// </summary>
public virtual void Eval(MockDirectoryWrapper dir)
{
}
/// <summary>
/// Reset should set the state of the failure to its default
/// (freshly constructed) state. Reset is convenient for tests
/// that want to create one failure object and then reuse it in
/// multiple cases. This, combined with the fact that <see cref="Failure"/>
/// subclasses are often anonymous classes makes reset difficult to
/// do otherwise.
/// <para/>
/// A typical example of use is
/// <code>
/// Failure failure = new Failure() { ... };
/// ...
/// mock.FailOn(failure.Reset())
/// </code>
/// </summary>
public virtual Failure Reset()
{
return this;
}
protected internal bool m_doFail;
public virtual void SetDoFail()
{
m_doFail = true;
}
public virtual void ClearDoFail()
{
m_doFail = false;
}
}
/// <summary>
/// Use this when throwing fake <see cref="IOException"/>,
/// e.g. from <see cref="Failure"/>.
/// </summary>
// LUCENENET: It is no longer good practice to use binary serialization.
// See: https://github.com/dotnet/corefx/issues/23584#issuecomment-325724568
#if FEATURE_SERIALIZABLE_EXCEPTIONS
[Serializable]
#endif
public class FakeIOException : IOException
{
public FakeIOException() { } // LUCENENET specific - added public constructor for serialization
#if FEATURE_SERIALIZABLE_EXCEPTIONS
/// <summary>
/// Initializes a new instance of this class with serialized data.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
protected FakeIOException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endif
}
}