chore: load Comet on demand - #5275
Conversation
|
please review it carefully |
|
Flagging one ordering risk with moving
That was previously guaranteed by Small repro against arrow-vector 19.0.0 showing the latch is real: // vectorFirst: touch an IntVector, then set the property
IntVector v = new IntVector("x", allocator);
v.allocateNew(1); v.set(0, 1); v.get(0); v.close();
System.setProperty("arrow.enable_null_check_for_get", "false");
// -> NULL_CHECKING_ENABLED = true (property ignored)
// propFirst: set the property, then touch the vector
// -> NULL_CHECKING_ENABLED = false (property honored)To be clear about scope, two things bound this:
So I'm not claiming a live bug. The concern is that a previously structural guarantee has become an incidental one that nothing enforces or documents: any future path that touches Arrow before instantiating Two smaller notes while I was in here:
|
|
Thanks @andygrove I also see tests failed because Comet initialized multiple times, it should be initialized once. It still may be static but only if Comet is enabled. Moving this to draft for now, it requires more efforts |
|
This seems like a complex/risky change just to suppress a log line saying that the driver has loaded. Does it matter that the driver loads even if Comet is disabled? If the user doesn't want the driver to load then they could just not specify the driver as a plugin in the first place? |
| // Arrow's BoundsChecking / NullCheckingForGet latch these in their static initializers, so | ||
| // later writes are silently ignored. Set them here (not in load()) so ordering does not | ||
| // depend on which Comet path runs first. These are pure JVM properties, no native lib needed. | ||
| if (!(boolean) CometConf.COMET_DEBUG_ENABLED().get()) { |
There was a problem hiding this comment.
The value of COMET_DEBUG_ENABLED can change over the lifetime of the driver - not sure of the impact here
Thanks @andygrove in fact its more than 1 line, so if Comet disabled the static init still calls which effectively preloads the Comet codebase, but doesn't enable it. It might confuse the user with Comet related entries even if Comet is disabled. Some cases were reported with failing on Comet class linking however comet was disabled in the first place. However the change is risky, and if you like I can split it into 2 PRs, simple hide message for now, and second discuss how to behave correctly for Comet code eager load. UPD: it would be good to split the work. It just came to my mind if we dont load Comet eagerly it would be more problematic to enable/disable Comet on the query level, which I personally find very useful. |
Which issue does this PR close?
Closes #5274
Rationale for this change
Fixes a misleading log line where the Rust "Comet native library version ... initialized" INFO appeared even when Comet was disabled (via
spark.comet.enabled=falseor the plugin's off-heap gate). The linecame from the JNI
initcall, which was fired unconditionally byNativeBase's eager static initializer. The static initializer ran on any first touch ofNativeBase— including the plugin's shutdown path(
NativeBase.releaseNative()) — so users who had opted out of Comet still saw the message.The fix makes native-library loading lazy: it now happens only when a Comet code path that actually needs JNI runs. The message is preserved for users who do use Comet; it just no longer fires when Comet is
disabled.
Changes
spark/src/main/java/org/apache/comet/NativeBase.javaload()call.arrow.enable_unsafe_memory_access,arrow.enable_null_check_for_get) atNativeBaseclass-load time. Arrow latches these inBoundsChecking/NullCheckingForGetstatic initializers, so writing them later is silently ignored. Keeping this in a static block (independent ofload()) preserves the previous ordering guarantee.These are pure JVM properties and do not require the native library.
protected NativeBase()constructor that calls a newensureLoaded(). This makesnew Native()— the entry point for every native code path — the natural trigger for the load. Documented at theconstructor that the library loads at most once per JVM per classloader (both
ensureLoaded()andload()short-circuit onloaded).isLoaded()now callsensureLoaded(), soCometSparkSessionExtensions.isCometLoadedalso triggers the load lazily. That method is already gated onCOMET_ENABLEDand platform/shuffle checks, so theload only fires when Comet is actually enabled.
releaseNative()reads theloadedfield directly instead of viaisLoaded(), so plugin shutdown does not force a lazy load of a library that was never used. Droppedthrows Throwableand wrappedrelease()in try/catch — shutdown never propagates.setLoaded()now also resetsloadErr. Without this, a test that forces a load failure (e.g.os.name=fooinCometSparkSessionExtensionsSuite) leaves a sticky throwable that laterisLoaded()callsrethrow, breaking downstream tests in the same JVM.
isFeatureEnabledandisObjectStoreSchemeSupportedare now Java wrappers that callensureLoaded()before delegating to private static native methodsisFeatureEnabledImpl/isObjectStoreSchemeSupportedImpl. This keeps the public API stable and preventsUnsatisfiedLinkErrorif a caller happens to hit them before any other trigger.native/core/src/lib.rsJava_org_apache_comet_NativeBase_isFeatureEnabledImplandJava_org_apache_comet_NativeBase_isObjectStoreSchemeSupportedImplto match the Java-side renames.Design rationale
Spark's
DriverPlugin.initjavadoc recommends "postponing [expensive operations] until the application has fully started." Deferring native-library load to first use aligns with that guidance and avoidspaying the cost (or emitting the log) in configurations where Comet is registered but not exercised.
NativeBaseis loaded at most once per JVM per classloader. The load is triggered by eithernew Native()(the constructor path used by every executor code path) orNativeBase.isLoaded(used by theplanning-time check). Both go through
ensureLoaded(), which isstatic synchronizedand guarded onloaded || loadErr != null.load()itself isstatic synchronizedand guarded onloaded. Concurrentfirst-touch callers serialize on the class monitor; the winner sets
loaded=truebefore returning; losers re-check and no-op.Behavior per configuration:
spark.plugins=org.apache.spark.CometPluginunset —NativeBaseis never referenced. No log.releaseNative()returns without loading. No log.spark.comet.enabled=false— extension rules return early insideisCometLoadedbefore touchingNativeBase.isLoaded. No log.isCometLoadedon the driver or firstnew Native()on the executor triggers the load. One log per JVM.Compatibility
NativeBase.releaseNative()losesthrows Throwable. Both callers are inPlugins.scala(Scala, no checked-exception impact).NativeBase.isFeatureEnabled/NativeBase.isObjectStoreSchemeSupportedkeep the same signature. The private native implementations were renamed with anImplsuffix; the JNI symbols follow.