Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion gluten-core/src/main/scala/org/apache/gluten/GlutenPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ private object GlutenDriverPlugin extends Logging {
}
}

private def setPredefinedConfigs(conf: SparkConf): Unit = {
// Visible for testing.
private[gluten] def setPredefinedConfigs(conf: SparkConf): Unit = {
// check memory off-heap enabled and size.
checkOffHeapSettings(conf)

Expand Down Expand Up @@ -134,6 +135,15 @@ private object GlutenDriverPlugin extends Logging {
((onHeapSize - (300 * 1024 * 1024)) *
conf.getDouble(GlutenCoreConfig.DYNAMIC_OFFHEAP_SIZING_MEMORY_FRACTION.key, 0.6d)).toLong
} else {
// Untracked memory mode skips the off-heap size requirement in checkOffHeapSettings, so
// the key may be absent here. Normalize it to 0 (mirroring the dynamic-sizing branch
// above) so downstream readers that read spark.memory.offHeap.size directly, e.g.
// VeloxListenerApi.onDriverStart, don't hit NoSuchElementException. Normal mode always has
// the key set because checkOffHeapSettings enforced it, so this only affects untracked
// mode without an explicit off-heap size.
if (!conf.contains(GlutenCoreConfig.SPARK_OFFHEAP_SIZE_KEY)) {

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.

we have checked the offHeap size while initing GlutenPlugin, this seems un neccesary? or we have any cases that meet this problem?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's still needed for untracked mode. You're right that we validate the off-heap size in checkOffHeapSettings, but that path only runs in normal mode: untracked mode returns early at L86-89 (the COLUMNAR_MEMORY_UNTRACKED branch), before the L92-102 size check, so spark.memory.offHeap.size can stay absent.

Repro: spark.gluten.memory.untracked=true with spark.memory.offHeap.size unset and dynamic sizing off → checkOffHeapSettings returns at L89 → the else-branch (main L136) calls the single-arg conf.getSizeAsBytes(SPARK_OFFHEAP_SIZE_KEY), which throws NoSuchElementException because SparkConf.get(String) reads the raw settings map and doesn't fall back to the ConfigEntry default. The added GlutenDriverPluginSuite test "setPredefinedConfigs does not throw in untracked mode without an off-heap size" fails without this change.

If it reads clearer, I can add a short comment at the untracked early-return noting the size isn't validated there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To confirm this reproduces on the real driver-init path and not only in the unit test:

With spark.plugins=org.apache.gluten.GlutenPlugin, spark.gluten.memory.untracked=true, and spark.memory.offHeap.size left unset, constructing a SparkContext runs GlutenDriverPlugin.init -> setPredefinedConfigs and fails with:

java.util.NoSuchElementException: spark.memory.offHeap.size

I checked this locally by wiring a test through new SparkContext(conf) with the real spark.plugins registration. It fails on the unfixed tree with that exception and passes with this change.

There are three places a boot-based test could live, each with a cost:

  1. Direct call to setPredefinedConfigs (what this PR does). Same input state and same throwing line as the real path, runs in gluten-core with no extra setup. It just does not enter through init.

  2. Full SparkContext boot in gluten-core. GlutenDriverPlugin.init calls Component.sorted(), which needs at least one META-INF/gluten-components file on the classpath, and gluten-core ships none (real backends live in the backend modules), so this needs a test-only dummy backend and a registration resource. Component also keeps a JVM-global graph with a one-shot load latch and no reset, and ComponentSuite registers an intentional dependency cycle without cleaning it up, so a boot suite sharing the test JVM hits UnsupportedOperationException: Cycle detected in the component graph. Making it stable needs a testing-only Component reset hook, with ShuffleManagerRegistry.clear() guarded by SparkTestUtil.isTesting as the precedent.

  3. Integration test in a backend module like backends-velox. That module ships a real VeloxBackend component file and has no graph-polluting suite, so a real spark.plugins boot works there without any of the option 2 machinery. The cost is that it goes through VeloxListenerApi.onDriverStart, so it needs the native build and only runs in CI, and the test would sit in a different module than the one-line fix.

I lean toward option 1 for this fix, and option 3 if we want a production-level boot test. Fine either way, tell me which you prefer.

conf.set(GlutenCoreConfig.SPARK_OFFHEAP_SIZE_KEY, "0")
}
conf.getSizeAsBytes(GlutenCoreConfig.SPARK_OFFHEAP_SIZE_KEY)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.gluten

import org.apache.gluten.config.GlutenCoreConfig

import org.apache.spark.SparkConf

import org.scalatest.funsuite.AnyFunSuite

class GlutenDriverPluginSuite extends AnyFunSuite {

test("setPredefinedConfigs does not throw in untracked mode without an off-heap size") {
// Untracked memory mode skips the off-heap size requirement in checkOffHeapSettings, so
// spark.memory.offHeap.size may be absent. setPredefinedConfigs must not read it with the
// single-arg getSizeAsBytes, which throws NoSuchElementException on a missing key.
val conf = new SparkConf(false)
.set("spark.master", "local[1]")
.set(GlutenCoreConfig.COLUMNAR_MEMORY_UNTRACKED.key, "true")
GlutenDriverPlugin.setPredefinedConfigs(conf)
assert(conf.getLong(GlutenCoreConfig.COLUMNAR_OFFHEAP_SIZE_IN_BYTES.key, -1L) == 0L)
// The raw key is normalized to 0 so downstream readers (e.g. VeloxListenerApi.onDriverStart)
// that call the single-arg getSizeAsBytes don't hit NoSuchElementException later.
assert(conf.getSizeAsBytes(GlutenCoreConfig.SPARK_OFFHEAP_SIZE_KEY) == 0L)
}

test("setPredefinedConfigs reads the configured off-heap size in the normal path") {
val conf = new SparkConf(false)
.set("spark.master", "local[1]")
.set(GlutenCoreConfig.SPARK_OFFHEAP_ENABLED_KEY, "true")
.set(GlutenCoreConfig.SPARK_OFFHEAP_SIZE_KEY, "512m")
GlutenDriverPlugin.setPredefinedConfigs(conf)
assert(
conf.getLong(GlutenCoreConfig.COLUMNAR_OFFHEAP_SIZE_IN_BYTES.key, -1L) == 512L * 1024 * 1024)
}
}
Loading