-
Notifications
You must be signed in to change notification settings - Fork 4
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
refactor(java):separate resposibilities on java engine so static methods can be exposed and do memory management that should work on Java 18 and above #198
Conversation
Class<?> cleanerClass = Class.forName("java.lang.ref.Cleaner"); | ||
Method registerMethod = cleanerClass.getMethod("register", Object.class, Runnable.class); | ||
|
||
// Avoid capturing the engine itself in the lambda, otherwise this prevents GC! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't spend 3 hours searching for this bug. I'm not salty at all
takeFeaturesFromResource(customEngine, "custom-strategy-tests.json"); | ||
Boolean result = customEngine.isEnabled(featureName, context); | ||
assertNotNull(result); | ||
assertEquals(expectedIsEnabled, result); | ||
} | ||
|
||
@Test | ||
void testResourceCleanup() throws InterruptedException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Borrowed but modified from the existing implementation to use a PhantomReference rather than grabbing big chunks of GC'd references and hoping Yggdrasil is one of them (nothing wrong with the other approach, this just feels a bit more direct)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks sane to me
import com.sun.jna.FunctionMapper; | ||
import com.sun.jna.NativeLibrary; | ||
|
||
class CamelToSnakeMapper implements FunctionMapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL about FunctionMapper
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was me yesterday haha
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice and clean, one ask for a small unit test, but looks good to me so far!
public String getFunctionName(NativeLibrary library, Method method) { | ||
String methodName = method.getName(); | ||
StringBuilder snakeCaseName = new StringBuilder(); | ||
|
||
for (char c : methodName.toCharArray()) { | ||
if (Character.isUpperCase(c)) { | ||
snakeCaseName.append('_').append(Character.toLowerCase(c)); | ||
} else { | ||
snakeCaseName.append(c); | ||
} | ||
} | ||
|
||
return snakeCaseName.toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good candidate for a unit test... but looks sane to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent suggestion, will do before merge
EDIT: Changed my mind. Gonna do this in another PR but before release
* feat(java): expose core version in java engine layer * fix(java): force native encoding to utf-8 so windows doesn't set it to something horrible (#200)
…#197) * feat(java): expose list_known_features * refactor(java):separate resposibilities on java engine so static methods can be exposed and do memory management that should work on Java 18 and above (#198) * refactor(java): rework java engine so that static methods can be exposed cleanly * feat(java): expose core version in java engine layer (#199) * fix(java): force native encoding to utf-8 so windows doesn't set it to something horrible (#200)
Sorry, this one does two things because they're really difficult to separate:
Most of this is so we can do cool things like this.
Refactor Java Engine
This splits up the responsibilities more cleanly (according to one ginger's opinions) of the Java engine. The three components do the following:
NativeLoader
Previously
YggdrasilFFI
, theNativeLoader
now only holds the responsibility for loading the Yggdrasil binary into memory. This is a purely static class since there's only ever one binary and that needs to be loaded before anything else can be done. Importantly, no pointer management is done at this level.NativeLoader
consumes the newCamelToSnakeMapper
to provide nice camelCase names to the Java code instead of using a decorator to deal with the snake_case names.UnleashFFI
This is the JNA interface to the binary. This exposes a Java interface to consume the Rust code. This does also provide a
getInstance
which returns the only valid implementation to access the binary. Consumers are free to mock this interface for testing.UnleashEngine
The code that binds the domain logic in Java to the domain logic in Rust. This does do pointer management. Specifically, one UnleashEngine in Java land holds one pointer to one Yggdrasil struct in Rust land. This component therefore also takes over the tests the YggdrasilFFI was previously doing to ensure the engine was properly cleaned up
Dynamically Mount Cleaner Instead of Finalizer
Sooo... small problem. Finalizers are deprecated in Java 17 and marked for removal. Later JVMs provide the option to turn them off entirely, which means our existing implementation just quietly leaks. The more delightful problem is that the alternative to finalizers, the
Cleaner
API, is only Java 9 and above. And we support Java 8.So we inject some code to check the JVM version. If you're Java 8 or below your finalizer executes as normal. If you're Java 9 or above, we use reflection to dynamically mount a cleaner and invoke that. Why reflection? Because we support source level compatibility for Java 8, which can't be compiled with direct references to
Cleaner
objects. Similar to the approach Spring uses