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

Merged
merged 2 commits into from
Nov 7, 2024
Merged

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

@soc soc force-pushed the topic/windows-ffi branch from 1a9a8f0 to 7c2ef55 Compare August 1, 2024 21:51
@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?

@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).

src/main/java/dev/dirs/impl/Windows.java Outdated Show resolved Hide resolved
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
Contributor

@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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@brcolow Made the changes!
(I'll let the CI run without System.loadLibrary("combase") first, will add it too if it complains.)

@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 soc force-pushed the topic/windows-ffi branch from 91a3c6f to 8285814 Compare October 21, 2024 17:26
@soc soc force-pushed the topic/windows-ffi branch from 8285814 to 2009d9e Compare October 21, 2024 17:30
@soc
Copy link
Collaborator Author

soc commented Oct 21, 2024

[error] Error: Total 0, Failed 0, Errors 0, Passed 0
[error] Error during tests:
[error] 	Running java with options -classpath D:\a\directories-jvm\directories-jvm\target\test-classes;D:\a\directories-jvm\directories-jvm\target\classes;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\junit\junit\4.13\junit-4.13.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\com\novocode\junit-interface\0.11\junit-interface-0.11.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\org\scala-sbt\test-interface\1.0\test-interface-1.0.jar;C:\Users\runneradmin\.sbt\boot\scala-2.12.19\org.scala-sbt\sbt\1.10.1\test-agent-1.10.1.jar;C:\Users\runneradmin\.sbt\boot\scala-2.12.19\org.scala-sbt\sbt\1.10.1\test-interface-1.0.jar sbt.ForkMain 53127 failed with exit code -1073740940
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 4 s, completed Oct 21, 2024, 5:31:04?PM

Not sure what this is about ...

@soc soc force-pushed the topic/windows-ffi branch from 2009d9e to fcf0332 Compare October 21, 2024 17:33
@brcolow

This comment was marked as outdated.

@soloturn
Copy link

soloturn commented Oct 24, 2024

[error] Error: Total 0, Failed 0, Errors 0, Passed 0
[error] Error during tests:
[error] 	Running java with options -classpath D:\a\directories-jvm\directories-jvm\target\test-classes;D:\a\directories-jvm\directories-jvm\target\classes;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\junit\junit\4.13\junit-4.13.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\com\novocode\junit-interface\0.11\junit-interface-0.11.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\org\scala-sbt\test-interface\1.0\test-interface-1.0.jar;C:\Users\runneradmin\.sbt\boot\scala-2.12.19\org.scala-sbt\sbt\1.10.1\test-agent-1.10.1.jar;C:\Users\runneradmin\.sbt\boot\scala-2.12.19\org.scala-sbt\sbt\1.10.1\test-interface-1.0.jar sbt.ForkMain 53127 failed with exit code -1073740940
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 4 s, completed Oct 21, 2024, 5:31:04?PM

Not sure what this is about ...

that looks like the jvm crashed. not too sure how to display it, but when you not let it fork, it might be easier to spot:

[info] compiling 2 Java sources to D:\a\directories-jvm\directories-jvm\target\test-classes ...
[info] done compiling
[error] Test dev.dirs.impl.UtilTest.testWindowsApplicationPath01 failed: java.lang.NoClassDefFoundError: Could not initialize class dev.dirs.impl.Windows, took 2.82 sec
[error] Test dev.dirs.DirectoriesTest.testUserDirectories failed: java.lang.UnsatisfiedLinkError: D:\a\directories-jvm\directories-jvm\target\task-temp-directory\sbt_5d637758\sbt_9d246c47\combase.dll: A dynamic link library (DLL) initialization routine failed, took 2.825 sec
[error]     at dev.dirs.impl.UtilTest.testWindowsApplicationPath01(UtilTest.java:125)
[error]     at jdk.internal.loader.NativeLibraries.load(Native Method)
[error]     at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
[error]     at jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:331)
[error]     at java.lang.reflect.Method.invoke(Method.java:580)
[error]     at jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:[19](https://github.com/soloturn/directories-jvm/actions/runs/11490359746/job/31980914902#step:4:20)7)

see #64.

@brcolow
Copy link
Contributor

brcolow commented Oct 24, 2024

[error] Error: Total 0, Failed 0, Errors 0, Passed 0
[error] Error during tests:
[error] 	Running java with options -classpath D:\a\directories-jvm\directories-jvm\target\test-classes;D:\a\directories-jvm\directories-jvm\target\classes;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\junit\junit\4.13\junit-4.13.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\com\novocode\junit-interface\0.11\junit-interface-0.11.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Users\runneradmin\AppData\Local\Coursier\Cache\v1\https\repo1.maven.org\maven2\org\scala-sbt\test-interface\1.0\test-interface-1.0.jar;C:\Users\runneradmin\.sbt\boot\scala-2.12.19\org.scala-sbt\sbt\1.10.1\test-agent-1.10.1.jar;C:\Users\runneradmin\.sbt\boot\scala-2.12.19\org.scala-sbt\sbt\1.10.1\test-interface-1.0.jar sbt.ForkMain 53127 failed with exit code -1073740940
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 4 s, completed Oct 21, 2024, 5:31:04?PM

Not sure what this is about ...

that looks like the jvm crashed. not too sure how to display it, but when you not let it fork, it might be easier to spot:

[info] compiling 2 Java sources to D:\a\directories-jvm\directories-jvm\target\test-classes ...
[info] done compiling
[error] Test dev.dirs.impl.UtilTest.testWindowsApplicationPath01 failed: java.lang.NoClassDefFoundError: Could not initialize class dev.dirs.impl.Windows, took 2.82 sec
[error] Test dev.dirs.DirectoriesTest.testUserDirectories failed: java.lang.UnsatisfiedLinkError: D:\a\directories-jvm\directories-jvm\target\task-temp-directory\sbt_5d637758\sbt_9d246c47\combase.dll: A dynamic link library (DLL) initialization routine failed, took 2.825 sec
[error]     at dev.dirs.impl.UtilTest.testWindowsApplicationPath01(UtilTest.java:125)
[error]     at jdk.internal.loader.NativeLibraries.load(Native Method)
[error]     at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
[error]     at jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:331)
[error]     at java.lang.reflect.Method.invoke(Method.java:580)
[error]     at jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:[19](https://github.com/soloturn/directories-jvm/actions/runs/11490359746/job/31980914902#step:4:20)7)

see #64.

Great tip. Looks like System.loadLibrary("combase") is needed :)

@soloturn
Copy link

soloturn commented Nov 5, 2024

@karianna can you do something about System.loadLibrary("combase"); not working correctly?

@soc soc mentioned this pull request Nov 7, 2024
- Drop all existing mechanisms for retrieving this info on Windows
- This increases the required Java version of the library to 22

Fixes #49.
@soc soc force-pushed the topic/windows-ffi branch from fcf0332 to 50ce28c Compare November 7, 2024 22:27
@soc soc merged commit b76e360 into main Nov 7, 2024
4 checks passed
@xenoterracide
Copy link

xenoterracide commented Nov 8, 2024

I know this is already merged, but this seems like the best place to comment. Java 22 is a blocker for me on using the library. Is there any possibility that the java 22 components could make like a "driver" and is an implementation that is used/preffered under the hood? Like we have log4j api but via adapters could have a number of implementations. There could also be a dumber (less correct) windows implementation that returns results? My use case is stuck on 11 bytecode (I want to use it for a gradle plugin, and gradle is on v8 but I think it needs to move forward and that 11 is acceptable). I do not understand the limitations that this would create.

oh, and when I say dumb, I mean really dumb, like just guess/make something up, only windows that are not EOL 10/11 and maybe not flexible the equivalent of not looking at XDG env var but just sticking things in the home directories as otherwise define.

@soc
Copy link
Collaborator Author

soc commented Nov 8, 2024

@xenoterracide If an older version works for you, stay on that.

@xenoterracide
Copy link

I wouldn't assume to know 🤷🏻‍♂️ and there's no guarantee an older version is going to get a maintenance release if there was a security vulnerability as the releases don't look like you're maintaining multiple versions. Good luck then!

@soloturn
Copy link

I wouldn't assume to know 🤷🏻‍♂️ and there's no guarantee an older version is going to get a maintenance release if there was a security vulnerability as the releases don't look like you're maintaining multiple versions. Good luck then!

that sentence has a lot of assumptions. the lib is not a springboot maven project which downloads the internet to build. it is a couple of java classes. might be better to first have a security vulnerability before addressing it @xenoterracide :)

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.

6 participants