Finish port of WindowsDirectory and NativeUnixDirectory, #1342#1346
Finish port of WindowsDirectory and NativeUnixDirectory, #1342#1346paulirwin wants to merge 4 commits into
Conversation
Complete the previously-excluded WindowsDirectory port from Lucene 4.8.1, replacing the original Java + C++/JNI native layer with Win32 P/Invoke (CreateFileW with FILE_FLAG_RANDOM_ACCESS, ReadFile with positioned NativeOverlapped reads, GetFileInformationByHandle, and SafeFileHandle for the handle lifecycle). Modernized to current Lucene.NET conventions (ReadInternal(Span<byte>), Position, DirectoryInfo/string ctors, Dispose pattern) to match SimpleFSDirectory. The read handle uses FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE. Upstream 4.8.1 used only READ | WRITE; FILE_SHARE_DELETE is added to match the SimpleFSDirectory read-path behavior from apache#1283 so an open read handle does not block deletion of the underlying file on Windows. The type is guarded to Windows via a runtime Constants.WINDOWS check (throwing PlatformNotSupportedException) and [SupportedOSPlatform("windows")] (under FEATURE_SUPPORTEDOSPLATFORMATTRIBUTE). Enabled AllowUnsafeBlocks in Lucene.Net.Misc for the fixed read buffer, and narrowed the Store compile exclusion to only the still-unfinished NativeUnixDirectory/NativePosixUtil. Added test coverage (the original Lucene type had none): TestWindowsDirectory runs the full BaseDirectoryTestCase suite (Windows-gated), plus bespoke tests for random-access positioned reads, independent clone reads, read-past-EOF, clone dispose not closing the shared handle, and concurrent multi-threaded clone reads. A cross-platform fixture verifies the platform guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ache#1342) Complete the previously-excluded NativeUnixDirectory and NativePosixUtil port from Lucene 4.8.1, replacing the original Java + C++/JNI native layer with libc P/Invoke. NativePosixUtil does open_direct (Linux O_DIRECT|O_NOATIME; macOS open + fcntl(F_NOCACHE)), pread, posix_fadvise/madvise with the Java->OS advice-ordinal remap. NativeUnixDirectory ports the direct-I/O directory and its IndexInput/IndexOutput faithfully (512-byte alignment, buffer dump/refill, seek, clone), with the Java NIO FileChannel operations replaced by libc pwrite/ftruncate/lseek and a block-aligned native buffer in place of the direct ByteBuffer. Modernized to the current Span-based ReadInternal/WriteBytes and Position/Dispose conventions. Both types are guarded to throw PlatformNotSupportedException on Microsoft Windows (Constants.WINDOWS + [UnsupportedOSPlatform("windows")]); they require Linux/macOS direct I/O. Removed the Store compile exclusions from Lucene.Net.Misc so both files now build. Added test coverage (the original Lucene types had none): a cross-platform guard fixture (verifies the Windows refusal), targeted direct-path tests (round-trip, concurrent clone reads, clone dispose not closing the shared descriptor), and TestNativeUnixDirectoryBase : BaseDirectoryTestCase running the full directory suite against the direct-I/O path. TestFsyncDoesntCreateNewFiles is overridden/skipped because NativeUnixDirectory is a composite/delegating directory: non-MERGE writes go through the delegate and are tracked in its staleFiles, so FSDirectory.Sync (which intersects with its own staleFiles) does not fsync them - the base test itself notes it does not handle composite directories. Unix functional tests are platform-gated and require an O_DIRECT-capable filesystem. Verified on Linux (WSL Ubuntu, .NET 10): 23 tests pass, 0 fail; on Windows the functional/base tests skip and the guard tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…che#1342) Two platform bugs caused TestNativeUnixDirectory* to fail (5 on macOS, all on ARM64 Linux): - macOS EACCES: the write path opened with open(path, O_RDWR|O_CREAT, 0666) through a fixed-signature P/Invoke, but C's open() is variadic so the mode argument rides the varargs ABI; on macOS (incl. Apple Silicon) the callee read garbage and created files without owner-read permission, so the subsequent O_RDONLY open failed with EACCES. Now pre-create the file with the BCL (applying the normal mode + umask) and open with the two-argument open() (no O_CREAT, no varargs). Dropped the broken 3-arg open and the O_CREAT/S_RW constants. - ARM64 Linux ENOTDIR: O_DIRECT was hardcoded to 0x4000, which is correct on x86/x86-64 but is O_DIRECTORY on Arm/Arm64 (the two are swapped; O_DIRECT there is 0x10000), so open() of a regular file failed with ENOTDIR. Now select O_DIRECT by RuntimeInformation.OSArchitecture. Also tighten the input refill EOF check from n < 0 to n <= 0: upstream used FileChannel.read() (returns -1 at EOF) but the port wraps libc pread() (returns 0 at EOF), so read-past-EOF would otherwise silently return zeros. Verified: TestNativeUnixDirectory* 23/23 pass on macOS arm64, Linux aarch64, and Linux x86-64 (the latter two in mcr.microsoft.com/dotnet/sdk:10.0 with an O_DIRECT-capable TMPDIR). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NightOwl888
left a comment
There was a problem hiding this comment.
Thanks.
Based on the benchmarks, I am starting to wonder whether we should aim to use native calls (or at least RandomAccess) instead of FileStream for writes on all of our FSDirectory implementations, since upstream they simply used FileChannel (which is a thin layer over native calls). Although, whatever solution we come up with, we should aim for getting the durability patched (#894) above all else.
Shouldn't we add these native calls to our existing native layer so we can utilize them to make a backport of RandomAccess for .NET Framework and other things that we are better off using native code for?
I didn't do a full review of this, but I found a few things we should address to make this implementation more complete.
|
Benchmarks for WindowsDirectory, before making PR feedback modifications above, are in: 64 MB file (mean, ms):
Notes:
|
…pache#1342) Per NightOwl888's review of apache#1346: - Surface IOException.HResult on the native-error paths so NativeFSLock (which inspects HResult to detect share/lock violations) keeps working: - errno-derived throws (NativePosixUtil.NewIOException/posix_fadvise; NativeUnixDirectory PWrite/FTruncate/FileSize) pass errno into the IOException(message, hresult) ctor. On Unix the BCL surfaces the raw errno as HResult (a FileStream share violation gives HResult == EWOULDBLOCK), so errno-as-HResult matches the convention. - Win32-derived throws (WindowsDirectory OpenFile/Read/Length) use Marshal.GetHRForLastWin32Error(). - Exception-wrap sites (WindowsDirectory.ReadInternal; NativeUnixDirectory Refill/Length/Clone) preserve the original HResult. Uses the IOException(message, hresult) ctor where possible because the HResult setter is protected on net462/netstandard2.0; the RuntimeException wrappers guard the setter with #if for those TFMs. - Implement NativeUnixIndexOutput.Checksum via a running CRC32 (like FSIndexOutput) instead of throwing NotSupportedException. The base-suite TestChecksum now passes against NativeUnixDirectory. - Move the LuceneNetSpecific test files into the Support folder: Store/Test{NativeUnix,Windows}Directory.cs -> Support/Store/... Not changed: the optional Put/Get micro-optimizations (Unsafe.CopyBlockUnaligned / ref byte), which the reviewer flagged as "benchmark to be sure it is worth it"; the current Span.CopyTo already lowers to memmove. Verified: builds on net10/net8/netstandard2.0/net462; TestNativeUnix/TestWindows pass 24/24 on macOS arm64 and 23/23 on Linux aarch64. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
I meant to reply earlier to your top-level review comment... replies below.
I like this, but it will not change the public API, so it can be broken out as a separate issue (even for post-beta-19) as a perf optimization opportunity.
Same as above, I think this can be broken out separately as a possible post-beta-19 perf optimization opportunity. In this particular case, these calls are very OS-dependent just like they are upstream (hence the names of the directories), so there's no harm in them being alongside the OS-specific directory code for now. That would be an easy refactor later as part of this possible perf improvement as a separate issue/PR. |
Finish port of WindowsDirectory and NativeUnixDirectory
Fixes #1342
Description
This PR finishes the work that was started on WindowsDirectory and NativeUnixDirectory.
Upstream, this code is a mix of Java and C++. Instead of adding in true native code, I finished the approach that was started of using P/Invoke to call the Win32/POSIX native functions.
This PR also adds tests for these types, which had no tests upstream. These tests all pass on Windows 11 x64, Ubuntu 24.04 x64 in WSL, Ubuntu 24.04 arm64 in Docker, and macOS 26 arm64.
Benchmark Results
These benchmarks are on macOS 26 arm64:
Notes:
O_DIRECT. It is worth reviewing to determine if this read performance should be improved.I have not yet run benchmarks on WindowsDirectory.
AI: This PR was mostly written using Claude Code, Opus 4.8.