Skip to content

Remove use of finalizers in JNA and improve concurrency for Memory, CallbackReference and NativeLibrary#1402

Merged
matthiasblaesing merged 5 commits into
java-native-access:masterfrom
matthiasblaesing:remove_finalizer
May 10, 2022
Merged

Remove use of finalizers in JNA and improve concurrency for Memory, CallbackReference and NativeLibrary#1402
matthiasblaesing merged 5 commits into
java-native-access:masterfrom
matthiasblaesing:remove_finalizer

Conversation

@matthiasblaesing

Copy link
Copy Markdown
Member

No description provided.

@matthiasblaesing matthiasblaesing force-pushed the remove_finalizer branch 2 times, most recently from fca09be to 77755e1 Compare December 28, 2021 14:25
@matthiasblaesing

Copy link
Copy Markdown
Member Author

The finalizer mechanism is deprecated for removal: https://mail.openjdk.java.net/pipermail/jdk-dev/2021-November/006279.html
So while #finalize was warned about for a long time, now removal must happen.

@dbwiddis

Copy link
Copy Markdown
Contributor

FYI, I've got a report of running out of memory that I'm pretty sure is related to excessive use of finalizer in the Memory allocations underlying HANDLEByReference and Structure classes when reading process and thread performance counters. I'll copy over the new Memory code and attempt to test the code and quantify the improvement, but I personally am now a lot more interested in this PR than I was before.

@matthiasblaesing

Copy link
Copy Markdown
Member Author

@dbwiddis - I reread the changes and the API is still intact. There is a potential behavioral change, but I think it should not be common that people overrode #dispose and expected it to be called from #finalize. It won't get better, so it would be nice if you could give it another look.

@dbwiddis dbwiddis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, but a few comments/suggestions.

if(ref instanceof CleanerRef) {
((CleanerRef) ref).clean();
}
} catch (InterruptedException ex) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we set Thread.currentThread.interrupt() here? I recognize that the thread will essentially terminate on an interrupt and it probably doesn't matter in practice, but will keep static analysis tools happier.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't get the intention. InterruptedException is raised if the waiting thread is interrupted (in this case the cleaner thread). Why would an analyser expect that the threads interrupts itself again?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Analyzers don't like when you catch an interrupted exception and do nothing. The general guidance is to either re-throw the exception or re-set the interrupted flag so that any upstream code recognizes the interruption.

As I said it probably doesn't matter in this case as the thread just terminates. (I'm not sure if it would terminate with a different exit code with that flag or not, or if that matters.)

This article says what I'm saying a lot better than I can: https://programming.guide/java/handling-interrupted-exceptions.html

Comment thread src/com/sun/jna/Cleaner.java
Comment thread src/com/sun/jna/Cleaner.java
Comment thread src/com/sun/jna/Memory.java
Comment thread src/com/sun/jna/Memory.java Outdated
@matthiasblaesing

Copy link
Copy Markdown
Member Author

Ok - this needs more work - the unittests became flaky. I first thought, that the cleaner implementation of ProxyObject was broken, but that does not explain unrelated failures. I suspect, that the Memory objects get quicker collected and thus we see race effects.

@matthiasblaesing

Copy link
Copy Markdown
Member Author

This is the problematic part:

    [junit] Exception in thread "JNA Cleaner" java.lang.Error: Invalid memory access
    [junit] 	at com.sun.jna.Native._getPointer(Native Method)
    [junit] 	at com.sun.jna.Native.getPointer(Native.java:2263)
    [junit] 	at com.sun.jna.Pointer.getPointer(Pointer.java:642)
    [junit] 	at com.sun.jna.platform.win32.COM.COMInvoker._invokeNativeInt(COMInvoker.java:37)
    [junit] 	at com.sun.jna.platform.win32.COM.Unknown.Release(Unknown.java:80)
    [junit] 	at com.sun.jna.platform.win32.COM.util.ProxyObject$Disposer.run(ProxyObject.java:747)
    [junit] 	at com.sun.jna.internal.Cleaner$CleanerRef.clean(Cleaner.java:127)
    [junit] 	at com.sun.jna.internal.Cleaner$1.run(Cleaner.java:60)
    [junit] Testsuite: com.sun.jna.platform.win32.COM.util.HybdridCOMInvocationTest

I still try to understand how we could end there.

@dbwiddis

dbwiddis commented Apr 24, 2022

Copy link
Copy Markdown
Contributor
    [junit] 	at com.sun.jna.platform.win32.COM.util.ProxyObject$Disposer.run(ProxyObject.java:747)

I suspect a race condition in ObjectFactory.disposeAll() (called in testOfficeInvocationProblemCOMUtil() in the problem test suite) which calls ProxyObject.dispose() (which no longer synchronizes on anything but is called by a method synchronizing on the factory's registered objects), vs. ProxyObject.Disposer.run() which synchronizes on the Disposer object itself.

@dbwiddis

Copy link
Copy Markdown
Contributor

FYI, long-running program with the Memory portion of this PR resolved the out-of-memory issues seen earlier with too many finalizers. So if we can resolve the test failures this looks like a good improvement.

@matthiasblaesing matthiasblaesing force-pushed the remove_finalizer branch 6 times, most recently from edb7975 to cb5be82 Compare May 5, 2022 18:24
@matthiasblaesing

Copy link
Copy Markdown
Member Author

So what happened: I removed the changes to the platform code, added stabilization to some of the regularly failing tests to get noise down or get better diagnostics. Multiple runs got through cleanly or reported errors that are "consistent" with breaks, that can happen in a shared system.

I intent to merge this at the end of the week end.

The following uses of finalizers were replaced by a Cleaner or removed
because they are unneeded:

- c.s.j.Memory#finalize
- c.s.j.CallbackReference#finalize
- c.s.j.NativeLibrary#finalize

The cleanup handling is moved to the #close method and #dispose is being
deprecated.

Anyone relying on #dispose being called from the GC has to take action
now! If a subclass needs a cleanup after this object goes out of scope,
it will need to register a cleaner.
@matthiasblaesing matthiasblaesing merged commit a8faaec into java-native-access:master May 10, 2022
@matthiasblaesing matthiasblaesing deleted the remove_finalizer branch May 16, 2022 18:34
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.

2 participants