This is an automated email from the ASF dual-hosted git repository.
zhztheplayer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git
The following commit(s) were added to refs/heads/main by this push:
new 9654b27eea [VL] Honor cancellation in final-stage iterators (#13495)
9654b27eea is described below
commit 9654b27eea55a859d25d9d0442cc08320c6d4d4a
Author: kevinwilfong <[email protected]>
AuthorDate: Fri Oct 30 10:13:17 2026 -0700
[VL] Honor cancellation in final-stage iterators (#13495)
---
.../backendsapi/velox/VeloxIteratorApi.scala | 1 +
.../execution/VeloxFinalStageIteratorSuite.scala | 104 +++++++++++++++++++++
2 files changed, 105 insertions(+)
diff --git
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxIteratorApi.scala
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxIteratorApi.scala
index 60605747e7..84a0e46b32 100644
---
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxIteratorApi.scala
+++
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxIteratorApi.scala
@@ -327,6 +327,7 @@ class VeloxIteratorApi extends IteratorApi with Logging {
}
.recyclePayload(batch => batch.close())
.collectLifeMillis(millis => pipelineTime += millis)
+ .asInterruptible(context)
.create()
}
// scalastyle:on argcount
diff --git
a/backends-velox/src/test/scala/org/apache/spark/sql/execution/VeloxFinalStageIteratorSuite.scala
b/backends-velox/src/test/scala/org/apache/spark/sql/execution/VeloxFinalStageIteratorSuite.scala
new file mode 100644
index 0000000000..b86e5d2f05
--- /dev/null
+++
b/backends-velox/src/test/scala/org/apache/spark/sql/execution/VeloxFinalStageIteratorSuite.scala
@@ -0,0 +1,104 @@
+/*
+ * 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.spark.sql.execution
+
+import org.apache.gluten.backendsapi.BackendsApiManager
+import org.apache.gluten.config.GlutenConfig
+import org.apache.gluten.substrait.SubstraitContext
+import org.apache.gluten.substrait.expression.{ExpressionBuilder,
ExpressionNode}
+import org.apache.gluten.substrait.plan.PlanBuilder
+import org.apache.gluten.substrait.rel.RelBuilder
+
+import org.apache.spark.{SparkConf, TaskKilledException}
+import org.apache.spark.sql.GlutenQueryTest
+import org.apache.spark.sql.catalyst.expressions.Attribute
+import org.apache.spark.sql.execution.metric.SQLMetrics
+import org.apache.spark.sql.test.SharedSparkSession
+import org.apache.spark.sql.vectorized.{ColumnarBatch, ColumnVector}
+import org.apache.spark.task.TaskResources
+
+import scala.collection.JavaConverters._
+
+class VeloxFinalStageIteratorSuite extends GlutenQueryTest with
SharedSparkSession {
+ override protected def sparkConf: SparkConf = {
+ super.sparkConf
+ .set("spark.plugins", "org.apache.gluten.GlutenPlugin")
+ .set("spark.default.parallelism", "1")
+ .set("spark.memory.offHeap.enabled", "true")
+ .set("spark.memory.offHeap.size", "1024MB")
+ .set("spark.ui.enabled", "false")
+ .set(GlutenConfig.COLUMNAR_MAX_BATCH_SIZE.key, "2")
+ // TaskResources.runUnsafe uses stage -1, which otherwise matches the
default dump stage.
+ .set(GlutenConfig.BENCHMARK_TASK_STAGEID.key, "0")
+ }
+
+ test("final-stage iterator checks cancellation between native calls") {
+ assume(BackendsApiManager.getBackendName == "velox")
+
+ val substraitContext = new SubstraitContext
+ val read = RelBuilder.makeReadRelForInputIterator(
+ Seq.empty[Attribute].asJava,
+ substraitContext,
+ substraitContext.nextOperatorId("input"))
+ val project = RelBuilder.makeProjectRel(
+ read,
+ Seq[ExpressionNode](ExpressionBuilder.makeLongLiteral(1L)).asJava,
+ substraitContext,
+ substraitContext.nextOperatorId("project"),
+ 0)
+ val plan = PlanBuilder.makePlan(substraitContext, Seq(project).asJava,
Seq("value").asJava)
+ val pipelineTime = SQLMetrics.createTimingMetric(sparkContext, "pipeline
time")
+ var metricsUpdates = 0
+
+ val usage = TaskResources.runUnsafe {
+ val context = TaskResources.getLocalTaskContext()
+ // Zero-column batches supply row counts for the native constant
projection. Multiple
+ // small batches leave output available without a Spark input iterator
checking cancellation.
+ val input = Seq.fill(3)(new ColumnarBatch(Array.empty[ColumnVector],
2)).iterator
+ val iterator =
BackendsApiManager.getIteratorApiInstance.genFinalStageIterator(
+ context,
+ Seq(input),
+ sparkContext.getConf,
+ plan,
+ pipelineTime,
+ metrics => {
+ assert(metrics != null)
+ metricsUpdates += 1
+ },
+ partitionIndex = 0,
+ materializeInput = false)
+
+ assert(iterator.hasNext)
+ val batch = iterator.next()
+ assert(batch.numCols() == 1)
+ assert(batch.numRows() == 2)
+ val reason = "cancel final-stage iterator"
+ context.markInterrupted(reason)
+ val killed = intercept[TaskKilledException] {
+ iterator.hasNext
+ }
+ assert(killed.reason == reason)
+ assert(metricsUpdates == 0)
+ // Leave the current output and native iterator open to exercise
task-resource cleanup.
+ TaskResources.getSharedUsage()
+ }
+
+ assert(metricsUpdates == 1)
+ assert(usage.current() == 0)
+ assert(!TaskResources.inSparkTask())
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]