Skip to content

List Linux directories with their file attributes through the FFM API - #2926

Closed
vogella wants to merge 1 commit into
eclipse-platform:masterfrom
vogella:filesystem-ffm
Closed

vogella wants to merge 1 commit into
eclipse-platform:masterfrom
vogella:filesystem-ffm

Conversation

@vogella

@vogella vogella commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

On Linux, PosixHandler.listDirectoryAndGetFileInfos() now reads a directory with opendir/readdir and stats each entry relative to it with statx through the FFM API, instead of one java.nio lookup per full path.
struct statx has the same layout on every architecture, so no compiled library is needed, and the results match the java.nio path exactly.
Listing 355984 entries takes 414 ms against 670 ms for java.nio and 523 ms for the JNI handler.
listDirectoryNames() keeps File.list(), since FFM saves only about 1 µs per directory there.
The bindings are generated by the included jextract script, which drops unused members; only statx is bound by hand to capture errno.

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    54 files  +  3      54 suites  +3   57m 0s ⏱️ + 2m 2s
 4 811 tests ±  0   4 789 ✅ +  1   22 💤 ±0  0 ❌  - 1 
12 324 runs  +261  12 171 ✅ +262  153 💤 ±0  0 ❌  - 1 

Results for commit c95e327. ± Comparison against base commit 6e4cd9b.

♻️ This comment has been updated with latest results.

@vogella
vogella marked this pull request as ready for review September 12, 2026 14:40
@HannesWell

Copy link
Copy Markdown
Member

On the long run it would probably make most sense to use FFM under Linux/POSIX only to implement the NativeHandler methods listDirectoryNames() and listDirectoryAndGetFileInfos() in the existing PosixHandler. All other methods can probably be implemented using Java NIO with comparable performance.

The existing native implementations to fetch/put file information are intended to be removed via

If you complete a FFM based implementation of PosixHandler.listDirectoryNames() and PosixHandler.listDirectoryAndGetFileInfos(), all native code based implementations could be removed in #2925 and the performance of the 'final' state of PosixHandler could be compared with the existing native implementation.

The bindings are hand-written rather than generated, unlike #2908. I ran jextract over these headers to check: it produces 2743 lines against 246, and 174 errors under this bundle's compiler settings (154 non-externalized string literals, 20 unused imports), which is why #2908 downgrades nonExternalizedStringLiteral and unusedImport from error to warning. It also emits no captureCallState, so every call here would still need a hand-written downcall handle to read errno, which is what tells a missing file from a real I/O error. The generated layouts do agree exactly with the hand-written offsets, which is a useful independent check on them. jextract earns its keep on WIN32_FIND_DATAW; for four statx fields and one dirent field it does not.

Since #2908 is now merged, these errors are now warnings already. Furthermore it's correct that the generated code is not the most compact one. But I think deterministically generated code is better to maintain on the long run, than AI generated one.
And I hope that we develop some additional tooling on top of jextract when generating bindings for larger code bases like SWT that helps to remove unused code from the generated bindings and to avoid warnings like missing non-externalized string markers. To avoid the latter, we could even try to improve Eclipse itself to support individuals JDT settings per source folder.

@vogella

vogella commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks, I have switched the bindings to jextract.

One exception: statx is bound by hand from the generated statx$address() and statx$descriptor(), with Linker.Option.captureCallState("errno") added, since jextract does not emit that option.

Without errno every failure looks the same and RefreshLocalVisitor deletes workspace resources whose statx failed with EACCES or EIO. The JNI handler reads errno today, so dropping the distinction would be a regression rather than a new limitation. That is similar to your adjustment in Lines 113-137 of the merged Win32Handler.

Since #2925 would change native code, I would rather not have both of us editing PosixHandler and LocalFileNativesManager at the same time: I can follow-up once #2925 is in.

@vogella

vogella commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@HannesWell how did you handle the additional warnings from the jextract generated code in your windows implementation? Just reset the quality gate? (How do I do this?)

@vogella
vogella force-pushed the filesystem-ffm branch 2 times, most recently from 0249e6f to 4fe0585 Compare September 21, 2026 13:24
@vogella

vogella commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

@HannesWell HannesWell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since #2925 would change native code, I would rather not have both of us editing PosixHandler and LocalFileNativesManager at the same time: I can follow-up once #2925 is in.

Thanks for your patience. That change currently has a problem for macOS but I'm working on a solution. Will let you know once it's completed.

how did you handle the additional warnings from the jextract generated code in your windows implementation? Just reset the quality gate? (How do I do this?)

Yes, exactly I reset the quality gate. In Jenkins there apers a Reset button in the overview page of a build next to the failed quality gates (usually a bit down the page).

I added a clean-up step to get rid of the additional warnings, see https://github.com/eclipse-platform/eclipse.platform/pull/2926/changes#diff-17d9390d9a5b073c8b584098a9193edeb880d06d3d86da553ccc9a1f5baa3318

That should work. Alternatively, you could also just add @SuppressWarnings("unused") annotations to the class or maybe just suppress all warnings.

Since the generated files are pretty large now and we want to trim the code to what's actually used (automatically) eventually. I suggest to remove unused code now already to avoid adding thousands of lines of code to git now only to later remove it.

On the long run it would probably make most sense to use FFM under Linux/POSIX only to implement the NativeHandler methods listDirectoryNames() and listDirectoryAndGetFileInfos() in the existing PosixHandler. All other methods can probably be implemented using Java NIO with comparable performance.

Furthermore I more and more believe we should just do this immediately with this PR.
And just implement listDirectoryAndGetFileInfos() in the PosixHandler now, i.e. with this PR.
A temporarily introduced separate handler probably doesn't have much users anyway.
That would hopefully also reduce the generated code.

I also challenge that listDirectoryNames() requires a native implementation and wonder if the simple Java implementation is comparable fast, as asked in

Comment on lines +11 to +20
cat > /tmp/LibC.h <<'HEADER'
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/stat.h>
#include <sys/stat.h>
#include <unistd.h>
HEADER

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know jextract can handle multiple header files at once.
So creating this temp header file is probably not necessary and you could just list the headers one by one as arguments to jextract.
Furthermore you can also define macros using:
-D --define-macro <macro>=<value> define <macro> to <value> (or 1 if <value> omitted)

Comment thread resources/bundles/org.eclipse.core.filesystem/generateLinuxH.sh
@vogella

vogella commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Almost all of it is statx.java (about 1600 lines), because jextract can't limit a struct to certain fields. Deleting accessors by hand would make the output no longer reproducible from the script, which works against the reason for using jextract. To make this reproducable I will put it in the scriptto strip the unused accessors after generation, in the same way it already removes unused imports.

@SuppressWarnings doesn't reach unused imports, because JDT reports those outside the class. That's why the script deletes them and adds @SuppressWarnings("all") to each class for the rest.

set -eu we should keep because shebang flags are ignored when the script is run as sh generateLinuxH.sh, and set -eu always applies.

I try to check listDirectoryNames() performance and it if is fine compared to the FFM version, I use it.

PosixHandler lists a directory and then asks java.nio for the attributes
of every entry by its full path. On Linux it now reads the directory with
opendir and readdir and stats each entry relative to it with statx,
through the Foreign Function & Memory API. The layout of struct statx is
the same on every architecture, so this needs no compiled library, and
the results match the java.nio path exactly, including for broken and
self referencing symbolic links.

Listing 355984 entries in 36444 directories takes 414 ms against 670 ms
for java.nio and 523 ms for the JNI LinuxFileHandler.

The bindings are generated by jextract with the script added here, which
drops the generated members nothing refers to. statx is bound by hand from
the generated descriptor and symbol, because reading errno needs
captureCallState, which jextract does not emit, and errno is what tells a
missing file from one that cannot be read.

Assisted-by: multiple AI agents and layers of automated tooling 🤖
@vogella vogella changed the title Read Linux file attributes through the FFM API List Linux directories with their file attributes through the FFM API Sep 21, 2026
@vogella
vogella requested a review from HannesWell September 21, 2026 23:00
@vogella

vogella commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Any additional feedback? Planning to merge tomorrow

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Embedded NULs, readdir and readlinkat errors, and runtime statx unavailability require correct handling before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Adds an FFM-based Linux directory reader to improve bulk file-attribute retrieval.

Changes:

  • Integrates opendir, readdir, and statx.
  • Adds generated Linux libc bindings and regeneration tooling.
  • Connects the optimized reader to PosixHandler.
File Description
resources/​bundles/​org.eclipse.core.filesystem/​src/​org/​eclipse/​core/​internal/​filesystem/​local/​nio/​PosixHandler.java Selects the Linux FFM reader.
resources/​bundles/​org.eclipse.core.filesystem/​src/​org/​eclipse/​core/​internal/​filesystem/​local/​nio/​LinuxDirectoryReader.java Implements directory enumeration and metadata retrieval.
resources/​bundles/​org.eclipse.core.filesystem/​src-gen/​org/​linux/​statx.java Defines the statx structure layout.
resources/​bundles/​org.eclipse.core.filesystem/​src-gen/​org/​linux/​statx_timestamp.java Defines statx timestamp accessors.
resources/​bundles/​org.eclipse.core.filesystem/​src-gen/​org/​linux/​LibC$shared.java Provides shared native layouts.
resources/​bundles/​org.eclipse.core.filesystem/​src-gen/​org/​linux/​LibC.java Binds required libc functions and constants.
resources/​bundles/​org.eclipse.core.filesystem/​src-gen/​org/​linux/​dirent.java Defines the directory-entry layout.
resources/​bundles/​org.eclipse.core.filesystem/​generateLinuxH.sh Regenerates and prunes Linux bindings.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +97 to +101
static IFileInfo[] listDirectoryAndGetFileInfos(String fileName) {
Scratch scratch = Scratch.CURRENT.get();
MemorySegment dir = LibC.opendir(scratch.path(fileName));
if (dir.address() == 0) {
return NO_INFOS;

@iloveeclipse iloveeclipse left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with the main direction of this change. "Nio" code / handler is supposed to provide "nio"-only implementation, as a fallback for any "native" implementation which might not work on some platform. Mixing now FFM into NIO is nkt the right way. We should get rid of problems we have (3 different native implementations which are hard ro maintain in C/Java mixed code, not create new problems by mixing FFM into non-native parts of code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why so strange package name? org.linux ? Please use proper namespace.
  2. Why it is in src-gen folder? It is confusing because such folders usually do not contain checked in code.
  3. License header is missing.


/**
* Lists a directory together with the attributes of its entries on Linux, with
* one {@code readdir} and one {@code statx} per entry through the Foreign

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File is placed under "nio" package but says it uses FFM instead of nio. Makes no sense?

* The native buffers these calls need, held per thread and reused, so that a
* listing allocates nothing per entry beyond its {@link FileInfo}.
*/
private static final class Scratch {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What "Scratch" type name means in this context?

@Override
public IFileInfo[] listDirectoryAndGetFileInfos(String fileName) {
if (LinuxDirectoryReader.isAvailable()) {
return LinuxDirectoryReader.listDirectoryAndGetFileInfos(fileName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this call here from "pure" NIO code to FFM based? It produces spaghetty like dependencies which are not obvious to detect/understand.

@vogella

vogella commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

I disagree with the main direction of this change. "Nio" code / handler is supposed to provide "nio"-only implementation, as a fallback for any "native" implementation which might not work on some platform. Mixing now FFM into NIO is nkt the right way. We should get rid of problems we have (3 different native implementations which are hard ro maintain in C/Java mixed code, not create new problems by mixing FFM into non-native parts of code.

I would also prefer to have a cleaner approach here, not mixing FFM migration with additional changes as I originally did.

@iloveeclipse

Copy link
Copy Markdown
Member

For the future work in this area, some thoughts / wishes from me:

  • main goal is to get rid of native C code by replacing it initially with 1:1 FFM alternative and to remove all native libraries we currently must build for each platform. These native binaries are current main PITA.
  • for Linux, try to use "fast" native handler code in the "linux" package as a base because it is faster and already cleaned up from Mac code paths (compared ro "unix" package).
  • if "linux" package is converted to FFM we can get rid of all the different linux fragments we build and ship.
  • if algoritms are ported to FFM, please keep all comments from old C code in FFM variant
  • do not mix NIO handler code with FFM, there should be always at least one "pure Java nio" handler, other way is OK, FFM handler can use Java NIO API if needed / doesn't affect performance.
  • then same conversion to FFM for Mac and Windows
  • note, "unix" code base contains today Linux and Mac specific code, but it should be renamed to "mac" and Linux specific code parts could be removed (the "linux" package, if migrated to FFM, would cover all Linux platforms, not just one as of today).
  • optimize for performance after "literal" FFM replace

@HannesWell HannesWell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleting accessors by hand would make the output no longer reproducible from the script, which works against the reason for using jextract. To make this reproducable I will put it in the scriptto strip the unused accessors after generation, in the same way it already removes unused imports.

Agree. It was just meant as an intermediate anticipation to avoid adding code now, that's deleted later.
Adding a bash script is fine more (I'd say it's not the ideal tool for that, but if it works that's nice to have for now).
However I checked the code and some methods are still unused.

@SuppressWarnings doesn't reach unused imports, because JDT reports those outside the class. That's why the script deletes them and adds @SuppressWarnings("all") to each class for the rest.

I cannot confirm that. When applying @SuppressWarnings("all") to a class even unused import warnings are gone for me.

set -eu we should keep because shebang flags are ignored when the script is run as sh generateLinuxH.sh, and set -eu always applies.

Acknowledged.

Update applied

I personally try to avoid dismissing request for changes from reviews, but instead ask for a subsequent review. I'd consider a dismiss similar to a rejection.

* listing allocates nothing per entry beyond its {@link FileInfo}.
*/
private static final class Scratch {
private static final ThreadLocal<Scratch> CURRENT = ThreadLocal.withInitial(Scratch::new);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a memory leak since the thread-local is never removed.

There is probably no way to reuse the memory permanently for a thread without adding such leak, since one cannot know if the memory will be reused in the future.
But since one Scratch can be reused for each file of a directory, that would already be a saving to reuse memory.

Comment on lines +107 to +117
MemorySegment entry = LibC.readdir(dir);
if (entry.address() == 0) {
return infos.toArray(IFileInfo[]::new);
}
// d_name is NUL terminated inside the struct, so bounding the entry by its
// declared size never cuts the name short.
MemorySegment name = dirent.d_name(entry.reinterpret(dirent.sizeof()));
String nameString = name.getString(0, PLATFORM_CHARSET);
if (!".".equals(nameString) && !"..".equals(nameString)) { //$NON-NLS-1$ //$NON-NLS-2$
infos.add(fetchFileInfo(scratch, dirFd, name, nameString));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have at least two native calls here I'd like to understand in detail, why implementing this part with native calls is more performant than using for example Files.readAttributes(path, PosixFileAttributes.class) for each entry? Maybe even in combination with using a DirectoryStream?
I assume the information is somehow contained in #2793, but I havn't fully worked through it.
Maybe also @iloveeclipse can help, when he has time.

For example in #2952, I've also introduced a mixed approach for the Win32Handler that uses Java NIO APIs where possible and just defers to native FFM calls when it has a conceptual advantage. At least in the very simple Benchmark I did for that PR, reimplementing the DosFileAttributes read with FFM was even marginally slower. But the implementation of Win32Handler.listDirectoryAndGetFileInfos() should still be round about twice as fast since it gets the exact filename information from the DirectoryStream and doesn't have to use the slower, search based method for it.

But I assume the situation is different for POSIX since the file-system is case-sensitive.
But if something similar is possible, we could probably save even more native code here.

Furthermore when comparing this to the code in #2793, it seems to be different. E.g. here statx is used, which isn't used in the current native code.
And according to AI that's not POSIX standard and only available on Linux 4.11 onwards, but also faster.

@HannesWell

HannesWell commented Sep 22, 2026 •

Copy link
Copy Markdown
Member
  • main goal is to get rid of native C code by replacing it initially with 1:1 FFM alternative and to remove all native libraries we currently must build for each platform. These native binaries are current main PITA.

Absolutely agree, but I'd go one step further and check where direct native calls are even necessary and where existing Java APIs can be used. Sometimes a more sophisticated use can already improve the performance.

I understood your comment in #302 (comment) and your work in #2793 in that way that the main benefit of the native Linux implementation is just in the native impls of listDirectoryAndGetFileInfos() (and maybe in listDirectoryNames()) and that the existing PosixHandler is suitable for everything else?
Is that understanding wrong?

Since the native binaries now exist I think there is no great gain in having intermediate steps.

  • there should be always at least one "pure Java nio" handler

Yes, but for that we have the DefaultHandler.
My goals is to have eventually three NativeHandler:

  • Win32Handler for Windows
  • PosixHandler for Linux and macOS.
  • DefaultHandler pure NIO and used for everything else, although I don't know where it would be used in reality.

For the Win32Handler we already have a mixture of FFM and NIO, since it evolved from the old DosHandler which was originally implemented in JNI but then moved to JNA (to also work under Windows on ARM) and is now using FFM.

The PosixHandler is used for macOS and Linux, so for some details they might need OS specific code-paths, but I hope the vast majority of code can be shared. And for those that I not, IMO it's fine to have separate OS specific helper classes like it's done here.

And I'd say that the same mixture is fine for the PosixHandler and FFM/native calls should be used where it makes sense and gives a sufficiently large performance gain compared to standard NIO.
But for that we (or at least the person implementing it) should check if and understand why a native method would perform better than the existing NIO APIs. For that it can also help to check the JDK's native implementations of said APIs. At least that's what I did for the Win32Handler. That can also help to find the right Java API for a needed native call.

@vogella

vogella commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

I don't know what the status here is.

We wait for Andreys performance tests? Or we perform performances tests ourself? @HannesWell you seem to have a clear understanding what you expect from this work, do you want to take over?

I feel like I'm trying to implement something with a lot of changing parts.

@HannesWell

Copy link
Copy Markdown
Member

We wait for Andreys performance tests? Or we perform performances tests ourself? @HannesWell you seem to have a clear understanding what you expect from this work, do you want to take over?

Yes, I''m fine to take over. Currently the first streamlining step of mine in #2925 needs an extension to support the BSD file-attributes used on mac. I've already started to work on that with Copilot, but got interrupted by other things.
Probably I'll have time to continue with that next week and the plan described before.

@vogella

vogella commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Closing as @HannesWell takes over

@vogella vogella closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants