Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Java 22's foreign function API for Windows #61

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

soc
Copy link
Collaborator

@soc soc commented Aug 1, 2024

  • Drop all existing mechanisms for retrieving this info on Windows
  • This increases the required Java version of the library to 22

try (var arena = Arena.ofConfined()) {
MemorySegment guidSegment = arena.allocate(GUID_LAYOUT);
if (CLSIDFromString(createSegmentFromString(folderId, arena), guidSegment) != 0) {
System.exit(-1);
Copy link
Collaborator Author

@soc soc Aug 1, 2024

Choose a reason for hiding this comment

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

return null or throw exception instead?

Copy link

Choose a reason for hiding this comment

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

Yeah - definitely don't want to exit. That was just for my little utility program.

@sideeffffect
Copy link

This increases the required Java version of the library to 22

Isn't this a deal breaker? I would imagine many people would like to run this library on Java 21 or Java 17 or even Java 11.
Maybe there's a way to have our cake and eat it too? We could make Multi-Release JAR https://github.com/sbt/sbt-multi-release-jar with fallback for < 22.

@soc
Copy link
Collaborator Author

soc commented Aug 2, 2024

@sideeffffect I totally agree that this may be a problem for many people at the moment.

The hope is that Java 22 will become an acceptable baseline in a few years and people will be able to adopt this version of the library.

Until then, they can stay on an older version.
(The Windows code in these versions is so sufficiently broken, that I don't consider it an acceptable fallback for a new version that comes with proper support through FFI.)

@soc soc force-pushed the topic/windows-ffi branch 3 times, most recently from 4333b43 to b6332c3 Compare August 2, 2024 19:50
@sideeffffect
Copy link

sideeffffect commented Aug 5, 2024

Until then, they can stay on an older version.

The Windows code in these versions is so sufficiently broken, that I don't consider it an acceptable fallback

But what about other fixes/improvements which are not related to Windows?

For example, Coursier (and/or scala-cli) needs to use a fork of directories-jvm. My hope was the by upstreaming the changes from the fork, the project could eventually come back to the upstream of directories-jvm.
But merging this without sbt-multi-release-jar would shut the door on that, because I don't think the project can depend on such recent version of Java (>=22). /cc @alexarchambault

@soc would you be open to a PR that sets up sbt-multi-release-jar?

@soc
Copy link
Collaborator Author

soc commented Aug 5, 2024

@sideeffffect I think we could reserve some versions (e. g. v27 - v29) for fixes to the old codebase, and release the one with working Windows support as v30, would that help?

I don't think the project can depend on such recent version of Java (>=22)

How is the the JVM delivered these days? The JVM version needed for the build tool shouldn't have any impact on the JVM version used for building users' code, right?

private static String getDir(String folderId) {
try (var arena = Arena.ofConfined()) {
MemorySegment guidSegment = arena.allocate(GUID_LAYOUT);
if (CLSIDFromString(createSegmentFromString(folderId, arena), guidSegment) != 0) {
Copy link
Collaborator Author

@soc soc Aug 5, 2024

Choose a reason for hiding this comment

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

I wonder if it made sense to compute this in Java using java.util.UUID.fromString(x).

I'm not sure though how we could convert that UUID into the struct SHGetKnownFolderPath needs though ...

Copy link

Choose a reason for hiding this comment

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

You can see what doing the conversion in java looks like here. https://github.com/maths22/directories-jvm/blob/ffm-poc/src/main/java22/com/maths22/directories/WindowsNativeUtil.java#L54

I'm not going to claim this is the best code for this-it could definitely be cleaned up substantially for readability, but it does function correctly.

@sideeffffect
Copy link

I think e.g. Coursier (a project using directories-jvm) needs to be able to run on whatever JVM the user has installed (>= 8), AFAIK.

reserve some versions

This would still lock (at least some) downstream dependencies on these "outdated" versions...

What downsides do you see in using sbt-multi-release-jar?

@soc
Copy link
Collaborator Author

soc commented Aug 5, 2024

I think e.g. Coursier (a project using directories-jvm) needs to be able to run on whatever JVM the user has installed (>= 8), AFAIK.

Yikes, they are still doing that? Ok.

This would still lock (at least some) downstream dependencies on these "outdated" versions...
What downsides do you see in using sbt-multi-release-jar?

That would still require an implementation that didn't require Java 22 for Windows, and I'm not seeing a good option there.

@sideeffffect
Copy link

implementation that didn't require Java 22 for Windows

It doesn't have to be perfect. Better half-broken than none at all. Very sad, but pragmatic approach IMHO.

@soc
Copy link
Collaborator Author

soc commented Aug 5, 2024

It doesn't have to be perfect. Better half-broken than none at all. Very sad, but pragmatic approach IMHO.

I think just staying on v2x of the library should be the thing to do in this case.

@maths22
Copy link

maths22 commented Sep 3, 2024

FWIW I have a multi-release jar implementation of approximately the same idea here: https://github.com/maths22/directories-jvm/tree/ffm-poc (though with implementations for the preview versions of all the intermediate java versions as well that had a usable preview)

@sideeffffect
Copy link

@maths22 would be lovely to incorporate this to upstream. What do you think @soc ?

@soc
Copy link
Collaborator Author

soc commented Sep 4, 2024

Some thoughts:

  • The commits look like a ton of work, code and effort (that I would have certainly appreciated more if I had known these existed before (@brcolow and me) duplicated parts of it).

  • I'm not sure if the additional build and test complexity is worth the temporary improvement, not to mention printing to System.out to tell people to add command line parameters leaves a sour taste.

  • Supporting Java 17 is certainly a benefit, but we also have code paths for 18, 19 and 20; that's doubling the (already too big) amount of code paths that would theoretically be tested, maintained and supported.

  • Regarding Java 18, 19, 20 code paths: I'm not sure if there is any reasonably-sized subset of people who balk at this version requiring Java 22, but using an experimental API in out-of-support Java 18 is just fine?

@maths22
Copy link

maths22 commented Sep 4, 2024

  • Honestly I meant to share what I had done with upstream after we had done our first release using it and made sure it didn't crash and burn, and then I forgot about checking on upstream until I needed to fix it to work on java 21 yesterday.
  • I'm not going to claim the logging to System.out is something that should be upstreamed; it was just the easiest way for me to add diagnostics for my internal purposes. If I was to actually file a PR I would think of a different method
  • I think we could reasonably drop java 18/19/20 and just fall back to the existing implementation if you really tried to run things on one of those; I implemented them because once I had MR jar support working it was fairly trivial to add them, but that doesn't mean they are actually worth supporting.
  • Annoyingly if we want to support java 21 (FFM is in preview there, but it's the current LTS) in addition to java 22 we need a multi-release jar with separate compliation of identical files for java 21 and 22 because java 22 won't load classes compiled with java 21 preview features (for good reason, but mildly annoying in this case)
  • Note on command-line parameters: it looks like in the final implementation java went with for java22, they don't require --enable-native-access; instead they just log the following warning, as seen in this project's build build on this branch.
WARNING: A restricted method in java.lang.foreign.AddressLayout has been called
WARNING: java.lang.foreign.AddressLayout::withTargetLayout has been called by dev.dirs.impl.Windows in an unnamed module
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled

Eventually that will need a solution, but that means for java 22+ you don't actually need any flags at the moment, and java itself logs the necessary warning (so we wouldn't need to do the equivalent ourselves). Java 17 needs both the --add-modules jdk.incubator.foreign flag and the --enable-native-access=ALL-UNNAMED, but java 21 only needs the --enable-preview flag (and will just then log the same warning).

private static String getDir(String folderId) {
try (var arena = Arena.ofConfined()) {
MemorySegment guidSegment = arena.allocate(GUID_LAYOUT);
if (CLSIDFromString(createSegmentFromString(folderId, arena), guidSegment) != 0) {
Copy link

Choose a reason for hiding this comment

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

You can see what doing the conversion in java looks like here. https://github.com/maths22/directories-jvm/blob/ffm-poc/src/main/java22/com/maths22/directories/WindowsNativeUtil.java#L54

I'm not going to claim this is the best code for this-it could definitely be cleaned up substantially for readability, but it does function correctly.

throw new AssertionError("failed converting string " + folderId + " to KnownFolderId");
}
MemorySegment path = arena.allocate(C_POINTER);
SHGetKnownFolderPath(guidSegment, 0, MemorySegment.NULL, path);
Copy link

@maths22 maths22 Sep 4, 2024

Choose a reason for hiding this comment

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

Per windows API docs,

The calling process is responsible for freeing this resource once it is no longer needed by calling CoTaskMemFree, whether SHGetKnownFolderPath succeeds or not.

Therefore, as currently implemented there would appear to be a minor memory leak.

(see https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath )

Copy link

@brcolow brcolow Sep 5, 2024

Choose a reason for hiding this comment

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

We can fix this:

Call CoTaskMemFree(path); after calling SHGetKnownFolderPath.

Add the following:

    private static class CoTaskMemFree {
        public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(C_POINTER);

        public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(
                    findOrThrow("CoTaskMemFree"), DESC);
    }
	
	 /**
     * {@snippet lang=c :
     * extern void CoTaskMemFree(LPVOID pv)
     * }
     */
    public static void CoTaskMemFree(MemorySegment pv) {
        var handle = CoTaskMemFree.HANDLE;
        try {
            handle.invokeExact(pv);
        } catch (Throwable throwable) {
           throw new AssertionError("should not reach here", throwable);
        }
    }

System.loadLibrary("combase"); may be necessary.

@soc
Copy link
Collaborator Author

soc commented Sep 5, 2024

  • Note on command-line parameters: it looks like in the final implementation java went with for java22, they don't require --enable-native-access; instead they just log the following warning, as seen in this project's build build on this branch.
    [...]
    Eventually that will need a solution, but that means for java 22+ you don't actually need any flags at the moment, and java itself logs the necessary warning (so we wouldn't need to do the equivalent ourselves).

I really wonder what's the "real" solution here, I can't imagine everyone just silencing the warning is the goal the Java devs had in mind...

@soloturn
Copy link

soloturn commented Oct 16, 2024

  • Note on command-line parameters: it looks like in the final implementation java went with for java22, they don't require --enable-native-access; instead they just log the following warning, as seen in this project's build build on this branch.
    [...]
    Eventually that will need a solution, but that means for java 22+ you don't actually need any flags at the moment, and java itself logs the necessary warning (so we wouldn't need to do the equivalent ourselves).

I really wonder what's the "real" solution here, I can't imagine everyone just silencing the warning is the goal the Java devs had in mind...

JNA seems to work with graalvm nowadays:

but my preferred approach would be to have 2 options in the library, and the dir-dev library knows this by its own: first, and preferred, JNA. and if it is compiled by graalvm or so, FFI.

i created a [https://github.com/microsoft/openjdk/issues/628 ticket here with microsofts openjdk], as i failed making a pull request [https://github.com/MovingBlocks/Terasology/pull/5284 using this library in terasology, because of the windows support]. @BenjaminAmos thinks terasology's approach to use JNA is more rubust, and i share that opinion.

@soc
Copy link
Collaborator Author

soc commented Oct 17, 2024

Status:

  • first two commits are merged
  • third and fourth commit will happen after rebase

@soc soc force-pushed the topic/windows-ffi branch 2 times, most recently from d25f61f to 91a3c6f Compare October 17, 2024 14:44
soc and others added 2 commits October 17, 2024 16:58
- Drop all existing mechanisms for retrieving this info on Windows
- This increases the required Java version of the library to 22

Fixes #49.
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.

5 participants