Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions src/Lucene.Net.Tests/Store/TestDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,120 @@ public virtual void ConcurrentIndexAccessThrowsWithoutSynchronizedStaleFiles()
}
}

// LUCENENET specific: Regression test for #1283. A read handle held open by SimpleFSDirectory or
// NIOFSDirectory must not block deletion of the underlying file. On Windows this only works because the
// read FileStream is opened with FileShare.Delete (the delete becomes a deferred/pending delete); on
// POSIX, delete-while-open always works. After the delete, the still-open handle must keep returning the
// original bytes without corruption. The assertion only has teeth on Windows (POSIX ignores share modes
// and would pass regardless), but the behavior asserted is cross-platform, so the test is not gated.
// MMapDirectory is intentionally excluded: its read handle still lacks FileShare.Delete in master and is
// addressed separately in #1267.
[Test]
[LuceneNetSpecific]
public virtual void TestDeleteWhileReadHandleOpen()
{
DirectoryInfo tempDir = CreateTempDir(GetType().Name);
var factories = new Func<Directory>[]
{
() => new SimpleFSDirectory(tempDir),
() => new NIOFSDirectory(tempDir),
};

byte[] expected = new byte[128];
for (int i = 0; i < expected.Length; i++)
{
expected[i] = (byte)i;
}

int n = 0;
foreach (var factory in factories)
{
using Directory dir = factory();
string name = "delete-while-read-" + n++;

using (IndexOutput output = dir.CreateOutput(name, NewIOContext(Random)))
{
output.WriteBytes(expected, expected.Length);
}

IndexInput input = dir.OpenInput(name, NewIOContext(Random));
try
{
// Read the first byte so the handle is genuinely active.
Assert.AreEqual(expected[0], input.ReadByte());

// The crux: deleting while a read handle is open must not throw.
try
{
dir.DeleteFile(name);
}
catch (Exception e)
{
Assert.Fail("DeleteFile threw while a read handle was open (missing FileShare.Delete?) for "
+ dir.GetType().Name + ": " + e);
}

// The still-open handle must keep returning the original bytes (no corruption, handle still valid).
byte[] actual = new byte[expected.Length - 1];
input.ReadBytes(actual, 0, actual.Length);
for (int i = 0; i < actual.Length; i++)
{
Assert.AreEqual(expected[i + 1], actual[i]);
}
}
finally
{
input.Dispose();
}
}
}

// LUCENENET specific: Regression test for #1283. The shared FSIndexOutput write handle (used by every
// FSDirectory subclass) is opened with FileShare.Delete, so deleting a file must succeed even while its
// write handle is still open. This makes the "if Lucene decides a file is deletable, the delete succeeds"
// guarantee hold unconditionally on Windows regardless of which of our own handles are open.
[Test]
[LuceneNetSpecific]
public virtual void TestDeleteWhileWriteHandleOpen()
{
DirectoryInfo tempDir = CreateTempDir(GetType().Name);
var factories = new Func<Directory>[]
{
() => new SimpleFSDirectory(tempDir),
() => new NIOFSDirectory(tempDir),
() => new MMapDirectory(tempDir),
};

int n = 0;
foreach (var factory in factories)
{
using Directory dir = factory();
string name = "delete-while-write-" + n++;

IndexOutput output = dir.CreateOutput(name, NewIOContext(Random));
try
{
output.WriteByte((byte)0x2A);
output.Flush();

// Deleting while the write handle is open must not throw.
try
{
dir.DeleteFile(name);
}
catch (Exception e)
{
Assert.Fail("DeleteFile threw while a write handle was open (missing FileShare.Delete?) for "
+ dir.GetType().Name + ": " + e);
}
}
finally
{
output.Dispose();
}
}
}


#pragma warning disable 612, 618
[Test]
Expand Down
7 changes: 6 additions & 1 deletion src/Lucene.Net/Store/FSDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,12 @@ public FSIndexOutput(FSDirectory parent, string name)
path: Path.Combine(parent.m_directory.FullName, name),
mode: FileMode.OpenOrCreate,
access: FileAccess.Write,
share: FileShare.ReadWrite,
// LUCENENET specific: Add FileShare.Delete (a deliberate divergence from upstream Java's
// RandomAccessFile, which omits it) so that every handle Lucene.NET opens on a file permits
// deletion. Windows blocks a delete unless all open handles allow Delete share; including it
// here makes "if Lucene decides a file is deletable, the delete succeeds" hold unconditionally,
// regardless of which handles are open, matching POSIX semantics (#1283).
share: FileShare.ReadWrite | FileShare.Delete,
bufferSize: CHUNK_SIZE);
isOpen = true;
}
Expand Down
10 changes: 8 additions & 2 deletions src/Lucene.Net/Store/SimpleFSDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,21 @@ public override IndexInput OpenInput(string name, IOContext context)
{
EnsureOpen();
var path = Path.Combine(Directory.FullName, name); // LUCENENET specific: changed to use string file name instead of allocating a FileInfo (#832)
var raf = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// LUCENENET specific: Add FileShare.Delete (a deliberate divergence from upstream Java's RandomAccessFile,
// which omits it) to match NIOFSDirectory and our other handles, giving Windows the same delete-while-open
// semantics POSIX already provides so index files can be deleted while a read handle is open (#1283).
var raf = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
return new SimpleFSIndexInput("SimpleFSIndexInput(path=\"" + path + "\")", raf, context);
}

public override IndexInputSlicer CreateSlicer(string name, IOContext context)
{
EnsureOpen();
var file = Path.Combine(Directory.FullName, name); // LUCENENET specific: changed to use string file name instead of allocating a FileInfo (#832)
var descriptor = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// LUCENENET specific: Add FileShare.Delete (a deliberate divergence from upstream Java's RandomAccessFile,
// which omits it) to match NIOFSDirectory and our other handles, giving Windows the same delete-while-open
// semantics POSIX already provides so index files can be deleted while a read handle is open (#1283).
var descriptor = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
return new IndexInputSlicerAnonymousClass(context, file, descriptor);
}

Expand Down
Loading