Josh Rosen created SPARK-58428:
----------------------------------
Summary: V2ExpressionBuilder loops forever on a
FAILED_TO_EVALUATE-tagged foldable, hanging the optimizer during DSv2 pushdown
Key: SPARK-58428
URL: https://issues.apache.org/jira/browse/SPARK-58428
Project: Spark
Issue Type: Bug
Components: SQL
Affects Versions: 4.1.0
Reporter: Josh Rosen
This is a report of an infinite loop optimizer hang present in Spark 4.1.0 and
later, reachable under default configuration during DSv2 pushdown.
h3. Repro
With stock configurations (ANSI mode on), run a pushdown-eligible query against
a DSv2 source where a filter contains an expression that constant-folds to an
error. This example uses a dummy JDBC source, but the same bug applies to other
DSv2 sources, too:
{code:java}
SET
spark.sql.catalog.d=org.apache.spark.sql.execution.datasources.v2.jdbc.JDBCTableCatalog;
SET spark.sql.catalog.d.url=jdbc:derby:memory:v2loopdb;
SET spark.sql.catalog.d.driver=org.apache.derby.jdbc.EmbeddedDriver;
SET spark.sql.catalog.d.create=true;
CREATE NAMESPACE IF NOT EXISTS d.test;
CREATE TABLE d.test.t (c INT);
SELECT count(*) FROM d.test.t WHERE c = 1; -- control: 0
SELECT * FROM d.test.t WHERE coalesce(c, 1 div 0) = 1; -- NEVER RETURNS{code}
This gets stuck at an infinite loop at the following stack:
{code:java}
"main" #1 [5123] prio=5 os_prio=31 cpu=32747.43ms elapsed=409.55s
tid=0x000000010324b5e0 nid=5123 runnable [0x000000016d585000]
java.lang.Thread.State: RUNNABLE
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.generateExpression(V2ExpressionBuilder.scala:98)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.$anonfun$generateExpressionWithNameByChildren$1(V2ExpressionBuilder.scala:465)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder$$Lambda/0x0000007802305cf8.apply(Unknown
Source)
at
scala.collection.StrictOptimizedIterableOps.flatMap(StrictOptimizedIterableOps.scala:118)
at
scala.collection.StrictOptimizedIterableOps.flatMap$(StrictOptimizedIterableOps.scala:105)
at scala.collection.immutable.Vector.flatMap(Vector.scala:116)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.generateExpressionWithNameByChildren(V2ExpressionBuilder.scala:465)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.generateExpressionWithName(V2ExpressionBuilder.scala:457)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.generateExpression(V2ExpressionBuilder.scala:152)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.generateExpression(V2ExpressionBuilder.scala:211)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.build(V2ExpressionBuilder.scala:39)
at
org.apache.spark.sql.catalyst.util.V2ExpressionBuilder.buildPredicate(V2ExpressionBuilder.scala:43)
at
org.apache.spark.sql.execution.datasources.v2.PushablePredicate$.unapply(DataSourceV2Strategy.scala:1012)
[...]
{code}
h3. Root cause
{{V2ExpressionBuilder.generateExpression}} has [constant-folding
logic|https://github.com/apache/spark/blob/8df89f20fc5c800ef4935c1b49a7525ec9df8921/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/V2ExpressionBuilder.scala#L96-L102]
that recursively calls itself:
{code:java}
case _ if expr.contextIndependentFoldable
&&
SQLConf.get.getConfByKeyStrict[Boolean]("spark.sql.optimizer.datasourceV2ExprFolding")
=>
val constantExpr = ConstantFolding.constantFolding(expr)
generateExpression(constantExpr, isPredicate) {code}
If a node carries the {{FAILED_TO_EVALUATE}} tag then
{{ConstantFolding.constantFolding}} passes it through unchanged, leading to an
infinite loop because the nested {{generateExpression}} call is in a tail
position and the Scala compiler's tail call elimination rewrites it to a jump.
As a result, the loop continues indefinitely because it doesn't hit stack
overflows or optimizer iteration limits.
h3. Prior art on the suggested fix
SPARK-50380 fixed the a similar "assumed a Literal came back" contract
violation in {{{}ReorderAssociativeOperator{}}}: instead of assuming that
{{.foldable}} implies that constant folding will return a {{{}Literal{}}}, it
performs an explicit {{isInstanceOf}} check/match on the result. I think we
should apply a similar check in V2ExpressionBuilder.generateExpression.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]