Skip to content

Commit e2e5d97

Browse files
authored
Add FileShare.Delete to SimpleFSDirectory and FSIndexOutput, apache#1283 (apache#1344)
1 parent 53cf888 commit e2e5d97

3 files changed

Lines changed: 128 additions & 3 deletions

File tree

src/Lucene.Net.Tests/Store/TestDirectory.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,120 @@ public virtual void ConcurrentIndexAccessThrowsWithoutSynchronizedStaleFiles()
490490
}
491491
}
492492

493+
// LUCENENET specific: Regression test for #1283. A read handle held open by SimpleFSDirectory or
494+
// NIOFSDirectory must not block deletion of the underlying file. On Windows this only works because the
495+
// read FileStream is opened with FileShare.Delete (the delete becomes a deferred/pending delete); on
496+
// POSIX, delete-while-open always works. After the delete, the still-open handle must keep returning the
497+
// original bytes without corruption. The assertion only has teeth on Windows (POSIX ignores share modes
498+
// and would pass regardless), but the behavior asserted is cross-platform, so the test is not gated.
499+
// MMapDirectory is intentionally excluded: its read handle still lacks FileShare.Delete in master and is
500+
// addressed separately in #1267.
501+
[Test]
502+
[LuceneNetSpecific]
503+
public virtual void TestDeleteWhileReadHandleOpen()
504+
{
505+
DirectoryInfo tempDir = CreateTempDir(GetType().Name);
506+
var factories = new Func<Directory>[]
507+
{
508+
() => new SimpleFSDirectory(tempDir),
509+
() => new NIOFSDirectory(tempDir),
510+
};
511+
512+
byte[] expected = new byte[128];
513+
for (int i = 0; i < expected.Length; i++)
514+
{
515+
expected[i] = (byte)i;
516+
}
517+
518+
int n = 0;
519+
foreach (var factory in factories)
520+
{
521+
using Directory dir = factory();
522+
string name = "delete-while-read-" + n++;
523+
524+
using (IndexOutput output = dir.CreateOutput(name, NewIOContext(Random)))
525+
{
526+
output.WriteBytes(expected, expected.Length);
527+
}
528+
529+
IndexInput input = dir.OpenInput(name, NewIOContext(Random));
530+
try
531+
{
532+
// Read the first byte so the handle is genuinely active.
533+
Assert.AreEqual(expected[0], input.ReadByte());
534+
535+
// The crux: deleting while a read handle is open must not throw.
536+
try
537+
{
538+
dir.DeleteFile(name);
539+
}
540+
catch (Exception e)
541+
{
542+
Assert.Fail("DeleteFile threw while a read handle was open (missing FileShare.Delete?) for "
543+
+ dir.GetType().Name + ": " + e);
544+
}
545+
546+
// The still-open handle must keep returning the original bytes (no corruption, handle still valid).
547+
byte[] actual = new byte[expected.Length - 1];
548+
input.ReadBytes(actual, 0, actual.Length);
549+
for (int i = 0; i < actual.Length; i++)
550+
{
551+
Assert.AreEqual(expected[i + 1], actual[i]);
552+
}
553+
}
554+
finally
555+
{
556+
input.Dispose();
557+
}
558+
}
559+
}
560+
561+
// LUCENENET specific: Regression test for #1283. The shared FSIndexOutput write handle (used by every
562+
// FSDirectory subclass) is opened with FileShare.Delete, so deleting a file must succeed even while its
563+
// write handle is still open. This makes the "if Lucene decides a file is deletable, the delete succeeds"
564+
// guarantee hold unconditionally on Windows regardless of which of our own handles are open.
565+
[Test]
566+
[LuceneNetSpecific]
567+
public virtual void TestDeleteWhileWriteHandleOpen()
568+
{
569+
DirectoryInfo tempDir = CreateTempDir(GetType().Name);
570+
var factories = new Func<Directory>[]
571+
{
572+
() => new SimpleFSDirectory(tempDir),
573+
() => new NIOFSDirectory(tempDir),
574+
() => new MMapDirectory(tempDir),
575+
};
576+
577+
int n = 0;
578+
foreach (var factory in factories)
579+
{
580+
using Directory dir = factory();
581+
string name = "delete-while-write-" + n++;
582+
583+
IndexOutput output = dir.CreateOutput(name, NewIOContext(Random));
584+
try
585+
{
586+
output.WriteByte((byte)0x2A);
587+
output.Flush();
588+
589+
// Deleting while the write handle is open must not throw.
590+
try
591+
{
592+
dir.DeleteFile(name);
593+
}
594+
catch (Exception e)
595+
{
596+
Assert.Fail("DeleteFile threw while a write handle was open (missing FileShare.Delete?) for "
597+
+ dir.GetType().Name + ": " + e);
598+
}
599+
}
600+
finally
601+
{
602+
output.Dispose();
603+
}
604+
}
605+
}
606+
493607

494608
#pragma warning disable 612, 618
495609
[Test]

src/Lucene.Net/Store/FSDirectory.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,12 @@ public FSIndexOutput(FSDirectory parent, string name)
521521
path: Path.Combine(parent.m_directory.FullName, name),
522522
mode: FileMode.OpenOrCreate,
523523
access: FileAccess.Write,
524-
share: FileShare.ReadWrite,
524+
// LUCENENET specific: Add FileShare.Delete (a deliberate divergence from upstream Java's
525+
// RandomAccessFile, which omits it) so that every handle Lucene.NET opens on a file permits
526+
// deletion. Windows blocks a delete unless all open handles allow Delete share; including it
527+
// here makes "if Lucene decides a file is deletable, the delete succeeds" hold unconditionally,
528+
// regardless of which handles are open, matching POSIX semantics (#1283).
529+
share: FileShare.ReadWrite | FileShare.Delete,
525530
bufferSize: CHUNK_SIZE);
526531
isOpen = true;
527532
}

src/Lucene.Net/Store/SimpleFSDirectory.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,21 @@ public override IndexInput OpenInput(string name, IOContext context)
100100
{
101101
EnsureOpen();
102102
var path = Path.Combine(Directory.FullName, name); // LUCENENET specific: changed to use string file name instead of allocating a FileInfo (#832)
103-
var raf = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
103+
// LUCENENET specific: Add FileShare.Delete (a deliberate divergence from upstream Java's RandomAccessFile,
104+
// which omits it) to match NIOFSDirectory and our other handles, giving Windows the same delete-while-open
105+
// semantics POSIX already provides so index files can be deleted while a read handle is open (#1283).
106+
var raf = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
104107
return new SimpleFSIndexInput("SimpleFSIndexInput(path=\"" + path + "\")", raf, context);
105108
}
106109

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

0 commit comments

Comments
 (0)