Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.nvidia.spark.rapids.shims

import java.io.FileNotFoundException
import java.util.concurrent.ExecutionException

import com.nvidia.spark.rapids.{FileSystemBytesReadTracker, MetricsBatchIterator, PartitionIterator}
import com.nvidia.spark.rapids.ScalableTaskCompletion.onTaskCompletion

Expand All @@ -33,7 +36,8 @@ import org.apache.spark.sql.vectorized.ColumnarBatch
class GpuDataSourceRDD(
sc: SparkContext,
@transient private val inputPartitions: Seq[Seq[InputPartition]],
partitionReaderFactory: PartitionReaderFactory
partitionReaderFactory: PartitionReaderFactory,
includeRefreshHint: Boolean = false
Comment thread
thirtiseven marked this conversation as resolved.
Outdated
) extends RDD[InternalRow](sc, Nil) {
import GpuDataSourceRDD.GpuDataSourceRDDPartition

Expand All @@ -60,12 +64,21 @@ class GpuDataSourceRDD(
private var currentIter: Option[Iterator[Object]] = None
private var currentIndex: Int = 0

override def hasNext: Boolean = {
override def hasNext: Boolean = try {
val result = currentIter.exists(_.hasNext) || advanceToNextIter()
if (!result) {
bytesReadTracker.update()
}
result
} catch {
case e: FileNotFoundException =>
throw GpuDataSourceRDD.withRecoveryHint(e, includeRefreshHint)
case e: ExecutionException =>
e.getCause match {
case cause: FileNotFoundException =>
throw GpuDataSourceRDD.withRecoveryHint(cause, includeRefreshHint)
case _ => throw e
}
}

override def next(): Object = {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

Expand Down Expand Up @@ -112,14 +125,43 @@ class GpuDataSourceRDD(
}

object GpuDataSourceRDD {
private val RECREATE_HINT = "recreating the Dataset/DataFrame involved"
private val REFRESH_HINT = "REFRESH TABLE"

private def withRecoveryHint(
e: FileNotFoundException,
includeRefreshHint: Boolean): FileNotFoundException = {
val message = Option(e.getMessage).getOrElse(e.toString)
if (message.contains(RECREATE_HINT) &&
(!includeRefreshHint || message.contains(REFRESH_HINT))) {
e
} else {
val recoveryHint = if (includeRefreshHint) {
"It is possible the underlying files have been updated. " +
"You can explicitly invalidate the cache in Spark by " +
"running 'REFRESH TABLE tableName' command in SQL or " +
"by recreating the Dataset/DataFrame involved."
} else {
"It is possible the underlying files have been updated. " +
"You can explicitly invalidate the cache in Spark by " +
"recreating the Dataset/DataFrame involved."
}
val enrichedException = new FileNotFoundException(s"$message\n$recoveryHint")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we make this translation version-aware before merging? This source is shared by every supported Spark build, but starting with Spark 4.0 the CPU file readers translate missing files through FileDataSourceV2.attachFilePath into a SparkException with condition FAILED_READ_FILE.FILE_NOT_EXIST and a path parameter. MetadataCacheSuite now asserts that structured error.

This branch always returns a plain FileNotFoundException with the Spark 3.x message, while the only end-to-end coverage is spark330 and the shared unit test explicitly expects that raw type. As a result, Spark 4.x GPU scans would still diverge from CPU behavior. I think this needs a shimmed translation with the owning file path, plus representative 4.x V1/V2 coverage.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

As it's a SparkUT fix, I'm fine with doing it in a follow-up.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. I’ll keep this SparkUT-focused PR scoped to Spark 3.x recovery guidance and handle Spark 4.x structured-error parity separately.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ok, please file an issue to track if so.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

enrichedException.initCause(e)
enrichedException
}
}

private case class GpuDataSourceRDDPartition(
override val index: Int,
inputPartitions: Seq[InputPartition]) extends Partition

def apply(
sc: SparkContext,
inputPartitions: Seq[InputPartition],
partitionReaderFactory: PartitionReaderFactory): GpuDataSourceRDD = {
new GpuDataSourceRDD(sc, inputPartitions.map(Seq(_)), partitionReaderFactory)
partitionReaderFactory: PartitionReaderFactory,
includeRefreshHint: Boolean = false): GpuDataSourceRDD = {
new GpuDataSourceRDD(
sc, inputPartitions.map(Seq(_)), partitionReaderFactory, includeRefreshHint)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,11 @@ case class GpuFileSourceScanExec(
logDebug(s"Using Datasource RDD, files are: " +
s"${prunedPartitions.flatMap(FilePartitionShims.getFiles).mkString(",")}")
// note we use the v2 DataSourceRDD instead of FileScanRDD so we don't have to copy more code
GpuDataSourceRDD(relation.sparkSession.sparkContext, locatedPartitions, readerFactory)
GpuDataSourceRDD(
relation.sparkSession.sparkContext,
locatedPartitions,
readerFactory,
includeRefreshHint = true)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,54 @@
spark-rapids-shim-json-lines ***/
package org.apache.spark.sql.rapids.suites

import org.apache.spark.sql.{MetadataCacheV1Suite, MetadataCacheV2Suite}
import com.nvidia.spark.rapids.{RapidsConf, RapidsReaderType}

import org.apache.spark.SparkException
import org.apache.spark.sql.{MetadataCacheSuite, MetadataCacheV1Suite, MetadataCacheV2Suite}
import org.apache.spark.sql.rapids.utils.RapidsSQLTestsTrait

private[suites] trait RapidsMetadataCacheRecoveryHintTests {
self: MetadataCacheSuite with RapidsSQLTestsTrait =>

protected def expectRefreshHint: Boolean

private def exceptionMessages(error: Throwable): String = {
val messages = new StringBuilder
var current = error
while (current != null) {
messages.append(current.toString)
messages.append('\n')
current = current.getCause
}
messages.toString()
}

Seq(RapidsReaderType.COALESCING, RapidsReaderType.MULTITHREADED).foreach { readerType =>
testRapids(s"missing ORC file includes recovery guidance - $readerType") {
withSQLConf(RapidsConf.ORC_READER_TYPE.key -> readerType.toString) {
withTempPath { location =>
spark.range(start = 0, end = 100, step = 1, numPartitions = 3)
.write.orc(location.getAbsolutePath)

val df = spark.read.orc(location.getAbsolutePath)
assert(df.count() == 100)
deleteOneFileInDirectory(location)

val messages = exceptionMessages(intercept[SparkException](df.count()))
assert(messages.contains("recreating the Dataset/DataFrame involved"))
assert(messages.contains("REFRESH TABLE") === expectRefreshHint)
}
}
}
}
}

class RapidsMetadataCacheV1Suite extends MetadataCacheV1Suite with RapidsSQLTestsTrait
with RapidsMetadataCacheRecoveryHintTests {
override protected val expectRefreshHint: Boolean = true
}

class RapidsMetadataCacheV2Suite extends MetadataCacheV2Suite with RapidsSQLTestsTrait
with RapidsMetadataCacheRecoveryHintTests {
override protected val expectRefreshHint: Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -372,19 +372,7 @@ class RapidsTestSettings extends BackendTestSettings {
enableSuite[RapidsFileSourceSQLInsertTestSuite]
enableSuite[RapidsDSV2SQLInsertTestSuite]
enableSuite[RapidsMetadataCacheV1Suite]
.exclude("SPARK-16336,SPARK-27961 Suggest fixing FileNotFoundException",
KNOWN_ISSUE("https://github.com/NVIDIA/cudf-spark/issues/15511. " +
"Recovery trigger: GPU ORC V1 missing-file errors include Spark-equivalent recreate " +
"guidance; P1."))
.exclude("SPARK-16337 temporary view refresh",
KNOWN_ISSUE("https://github.com/NVIDIA/cudf-spark/issues/15511. " +
"Recovery trigger: GPU ORC V1 missing-file errors include Spark-equivalent REFRESH " +
"and recreate guidance; P1."))
enableSuite[RapidsMetadataCacheV2Suite]
.exclude("SPARK-16336,SPARK-27961 Suggest fixing FileNotFoundException",
KNOWN_ISSUE("https://github.com/NVIDIA/cudf-spark/issues/15511. " +
"Recovery trigger: GPU ORC V2 missing-file errors include Spark-equivalent recreate " +
"guidance; P1."))
enableSuite[RapidsFileSourceStrategySuite]
.exclude("partitioned table - after scan filters", ADJUST_UT("Replaced by testRapids version that checks GpuFilterExec residual filters."))
.exclude("[SPARK-16818] partition pruned file scans implement sameResult correctly", KNOWN_ISSUE("https://github.com/NVIDIA/cudf-spark/issues/15161"))
Expand Down
Loading