Skip to content

[GLUTEN-12616][CORE] Guard SparkResourceUtil.getTaskSlots against non-positive task cpus#12617

Open
LuciferYang wants to merge 2 commits into
apache:mainfrom
LuciferYang:fix/task-slots-divide-by-zero
Open

[GLUTEN-12616][CORE] Guard SparkResourceUtil.getTaskSlots against non-positive task cpus#12617
LuciferYang wants to merge 2 commits into
apache:mainfrom
LuciferYang:fix/task-slots-divide-by-zero

Conversation

@LuciferYang

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

SparkResourceUtil.getTaskSlots computed executorCores / taskCores with no guard. GlutenDriverPlugin.init reads the slot count and divides by it, and four other callers (MemoryTargets, ColumnarShuffleWriter, and the Celeborn and Uniffle writers) use it as a denominator too. Two invalid configs crash there with an opaque error.

When spark.task.cpus > spark.executor.cores, the integer division gives 0, so a caller's offHeapSize / taskSlots throws ArithmeticException: / by zero. When spark.task.cpus = 0, getTaskSlots itself throws, because it reads the value with raw conf.getInt, which skips Spark's CPUS_PER_TASK.checkValue(_ > 0) (that check runs only on the typed conf.get(CPUS_PER_TASK)).

The plugin runs at the PluginContainer step in SparkContext, before createTaskScheduler. So Gluten throws before Spark's own validation (validateTaskCpusLargeEnough and the CPUS_PER_TASK check) can report the real problem, and the user gets a Gluten ArithmeticException stack trace instead of Spark's clear message. Both configs are invalid and Spark rejects them regardless, so the job does not start either way. The point is that Gluten should not misattribute the failure to itself with a divide-by-zero.

getTaskSlots now returns a single slot when taskCores <= 0, and floors the quotient at 1 otherwise, so it never divides by a non-positive value and never returns 0. Gluten then defers to Spark for the error message.

How was this patch tested?

Added SparkResourceUtilSuite with four tests: task.cpus greater than executor cores floors to one slot, task.cpus=0 does not divide by zero, 8 / 2 gives 4, and the default is one slot per core. The first two fail on the current code (they hit the two divide-by-zero paths) and pass after the fix.

Closes #12616

…-positive task cpus

getTaskSlots computed executorCores / taskCores with no guard. GlutenDriverPlugin.init
reads the slot count and divides by it (and four other callers use it as a denominator),
so two invalid configs crash there with an opaque ArithmeticException:

- spark.task.cpus > spark.executor.cores makes the quotient 0, so a caller's
  offHeapSize / taskSlots throws.
- spark.task.cpus = 0 makes getTaskSlots itself throw; it reads the value with raw
  conf.getInt, which bypasses Spark's CPUS_PER_TASK.checkValue(_ > 0).

The plugin runs before createTaskScheduler, so Gluten throws before Spark's own
validation (validateTaskCpusLargeEnough / CPUS_PER_TASK) can report the real
misconfiguration with a clear message. Return a single slot for taskCores <= 0 and
floor the quotient at 1 otherwise, deferring to Spark for the error text.

Add SparkResourceUtilSuite covering both guarded branches and the normal paths.
Copilot AI review requested due to automatic review settings July 24, 2026 05:40
@github-actions github-actions Bot added the CORE works for Gluten Core label Jul 24, 2026
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI 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.

Pull request overview

This PR hardens SparkResourceUtil.getTaskSlots so Gluten plugin initialization never triggers a divide-by-zero or returns 0 task slots for invalid spark.task.cpus settings, letting Spark’s own validation surface the proper user-facing error.

Changes:

  • Guard getTaskSlots against spark.task.cpus <= 0 by returning 1.
  • Floor executorCores / taskCores at 1 to avoid returning 0 when spark.task.cpus > executor cores.
  • Add a new SparkResourceUtilSuite covering the guarded behaviors and normal cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
gluten-core/src/main/scala/org/apache/spark/util/SparkResourceUtil.scala Adds guards to prevent / by zero and zero-slot results from invalid CPU-per-task configs.
gluten-core/src/test/scala/org/apache/spark/util/SparkResourceUtilSuite.scala Introduces unit tests for getTaskSlots behavior across invalid and typical configurations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +52 to +55
test("getTaskSlots returns one core per slot by default") {
val conf = new SparkConf(false).set("spark.master", "local[1]")
assert(SparkResourceUtil.getTaskSlots(conf) == 1)
}

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.

Good catch. local[1] didn't actually distinguish the default behavior since 1 slot comes out regardless of the logic under test. Changed it to local[8] with spark.task.cpus unset, asserting 8 slots, so it now validates one-slot-per-core when task cpus defaults to 1.

…e master

The default-behavior test used local[1], which yields 1 slot regardless of the
logic under test. Use local[8] with spark.task.cpus unset and assert 8 slots, so
it validates one-slot-per-core when task cpus defaults to 1.
Copilot AI review requested due to automatic review settings July 24, 2026 10:34
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

val executorCores = SparkResourceUtil.getExecutorCores(conf)
val taskCores = conf.getInt("spark.task.cpus", 1)
executorCores / taskCores
if (taskCores <= 0) {

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.

the task cores has been checked while setting the configs, do we need this check again?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CORE works for Gluten Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GlutenPlugin init throws opaque ArithmeticException for invalid spark.task.cpus before Spark validates it

3 participants