Skip to content

Conversation

@ankurdave
Copy link
Contributor

@ankurdave ankurdave commented Oct 22, 2021

What changes were proposed in this pull request?

The previous PR #34245 assumed task completion listeners are registered bottom-up. ParquetFileFormat#buildReaderWithPartitionValues() violates this assumption by registering a task completion listener to close its output iterator lazily. Since task completion listeners are executed in reverse order of registration, this listener always runs before other listeners. When the downstream operator contains a Python UDF and the off-heap vectorized Parquet reader is enabled, this results in a use-after-free that causes a segfault.

The fix is to close the output iterator using FileScanRDD's task completion listener.

Why are the changes needed?

Without this PR, the Python tests introduced in #34245 are flaky (see details in thread). They intermittently fail with a segfault.

Does this PR introduce any user-facing change?

No.

How was this patch tested?

Repeatedly ran one of the Python tests introduced in #34245 using the commands below. Previously, the test was flaky and failed after about 50 runs. With this PR, the test has not failed after 1000 runs.

./build/sbt -Phive clean package && ./build/sbt test:compile
seq 1000 | parallel -j 8 --halt now,fail=1 'echo {#}; python/run-tests --testnames pyspark.sql.tests.test_udf'

@github-actions github-actions bot added the SQL label Oct 22, 2021
@ankurdave
Copy link
Contributor Author

iter.asInstanceOf[Iterator[InternalRow]]
} catch {
case e: Throwable =>
// SPARK-23457: In case there is an exception in initialization, close the iterator to
Copy link
Contributor

Choose a reason for hiding this comment

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

shall we let the caller FileScanRDD close the iterator when hitting errors?

Copy link
Contributor Author

@ankurdave ankurdave Oct 22, 2021

Choose a reason for hiding this comment

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

In general, I think FileScanRDD does close the iterator when hitting exceptions, because it uses a task completion listener to do so. The only case where it will not close the iterator is when the exception prevents FileScanRDD from getting a reference to the iterator, as is the case here.

Copy link
Contributor

Choose a reason for hiding this comment

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

ah I see!

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Test build #144538 has finished for PR 34369 at commit 29f5e77.

  • This patch fails Spark unit tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/49009/

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Kubernetes integration test unable to build dist.

exiting with code: 1
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/49011/

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Kubernetes integration test status failure
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/49009/

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/49015/

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Kubernetes integration test status failure
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/49015/

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Test build #144543 has finished for PR 34369 at commit c09cf5e.

  • This patch fails Spark unit tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@dongjoon-hyun
Copy link
Member

Thank you for pinging me, @ankurdave .

@dongjoon-hyun
Copy link
Member

cc @sunchao

iter.closeIfNeeded()
case iter: Closeable =>
iter.close()
case _ => // do nothing
Copy link
Member

@dongjoon-hyun dongjoon-hyun Oct 22, 2021

Choose a reason for hiding this comment

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

When does this happen? Only when currentIterator is null?

Copy link
Contributor Author

@ankurdave ankurdave Oct 22, 2021

Choose a reason for hiding this comment

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

There are currently two cases aside from null:

  • OrcFileFormat produces an ordinary non-Closeable Iterator due to unwrapOrcStructs().
  • The user can create a FileScanRDD with an arbitrary readFunction that does not return a Closeable Iterator.

It would be ideal if we could disallow these cases and require the iterator to be Closeable, but it seems that would require changing public APIs.

@SparkQA
Copy link

SparkQA commented Oct 22, 2021

Test build #144544 has finished for PR 34369 at commit 559224d.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

Copy link
Member

@sunchao sunchao left a comment

Choose a reason for hiding this comment

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

LGTM - just one minor question

private lazy val internalIter = readCurrentFile()
// vectorized Parquet reader. Here we use a lazily initialized variable to delay the
// creation of iterator so that we will throw exception in `getNext`.
private var internalIter: Iterator[InternalRow] = null
Copy link
Member

Choose a reason for hiding this comment

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

hm why is this change necessary?

Copy link
Contributor Author

@ankurdave ankurdave Oct 22, 2021

Choose a reason for hiding this comment

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

If the downstream operator never pulls any rows from the iterator, then the first time we access internalIter will be when close() is called. If internalIter is a lazy val, this will trigger a call to readCurrentFile(), which is unnecessary and may throw. Changing internalIter from a lazy val to a var lets us avoid this unnecessary call.

Several tests fail without this change, including AvroV1Suite.

Copy link
Member

Choose a reason for hiding this comment

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

Got it. Thanks

@HyukjinKwon
Copy link
Member

HyukjinKwon commented Oct 25, 2021

Merged to master and branch-3.2.

HyukjinKwon pushed a commit that referenced this pull request Oct 25, 2021
…ner lazily

### What changes were proposed in this pull request?

The previous PR #34245 assumed task completion listeners are registered bottom-up. `ParquetFileFormat#buildReaderWithPartitionValues()` violates this assumption by registering a task completion listener to close its output iterator lazily. Since task completion listeners are executed in reverse order of registration, this listener always runs before other listeners. When the downstream operator contains a Python UDF and the off-heap vectorized Parquet reader is enabled, this results in a use-after-free that causes a segfault.

The fix is to close the output iterator using FileScanRDD's task completion listener.

### Why are the changes needed?

Without this PR, the Python tests introduced in #34245 are flaky ([see details in thread](#34245 (comment))). They intermittently fail with a segfault.

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

Repeatedly ran one of the Python tests introduced in #34245 using the commands below. Previously, the test was flaky and failed after about 50 runs. With this PR, the test has not failed after 1000 runs.

```sh
./build/sbt -Phive clean package && ./build/sbt test:compile
seq 1000 | parallel -j 8 --halt now,fail=1 'echo {#}; python/run-tests --testnames pyspark.sql.tests.test_udf'
```

Closes #34369 from ankurdave/SPARK-37089.

Authored-by: Ankur Dave <[email protected]>
Signed-off-by: Hyukjin Kwon <[email protected]>
(cherry picked from commit 1fc1d07)
Signed-off-by: Hyukjin Kwon <[email protected]>
sunchao pushed a commit to sunchao/spark that referenced this pull request Dec 8, 2021
…ner lazily

### What changes were proposed in this pull request?

The previous PR apache#34245 assumed task completion listeners are registered bottom-up. `ParquetFileFormat#buildReaderWithPartitionValues()` violates this assumption by registering a task completion listener to close its output iterator lazily. Since task completion listeners are executed in reverse order of registration, this listener always runs before other listeners. When the downstream operator contains a Python UDF and the off-heap vectorized Parquet reader is enabled, this results in a use-after-free that causes a segfault.

The fix is to close the output iterator using FileScanRDD's task completion listener.

### Why are the changes needed?

Without this PR, the Python tests introduced in apache#34245 are flaky ([see details in thread](apache#34245 (comment))). They intermittently fail with a segfault.

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

Repeatedly ran one of the Python tests introduced in apache#34245 using the commands below. Previously, the test was flaky and failed after about 50 runs. With this PR, the test has not failed after 1000 runs.

```sh
./build/sbt -Phive clean package && ./build/sbt test:compile
seq 1000 | parallel -j 8 --halt now,fail=1 'echo {#}; python/run-tests --testnames pyspark.sql.tests.test_udf'
```

Closes apache#34369 from ankurdave/SPARK-37089.

Authored-by: Ankur Dave <[email protected]>
Signed-off-by: Hyukjin Kwon <[email protected]>
(cherry picked from commit 1fc1d07)
Signed-off-by: Hyukjin Kwon <[email protected]>
catalinii pushed a commit to lyft/spark that referenced this pull request Feb 22, 2022
…ner lazily

### What changes were proposed in this pull request?

The previous PR apache#34245 assumed task completion listeners are registered bottom-up. `ParquetFileFormat#buildReaderWithPartitionValues()` violates this assumption by registering a task completion listener to close its output iterator lazily. Since task completion listeners are executed in reverse order of registration, this listener always runs before other listeners. When the downstream operator contains a Python UDF and the off-heap vectorized Parquet reader is enabled, this results in a use-after-free that causes a segfault.

The fix is to close the output iterator using FileScanRDD's task completion listener.

### Why are the changes needed?

Without this PR, the Python tests introduced in apache#34245 are flaky ([see details in thread](apache#34245 (comment))). They intermittently fail with a segfault.

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

Repeatedly ran one of the Python tests introduced in apache#34245 using the commands below. Previously, the test was flaky and failed after about 50 runs. With this PR, the test has not failed after 1000 runs.

```sh
./build/sbt -Phive clean package && ./build/sbt test:compile
seq 1000 | parallel -j 8 --halt now,fail=1 'echo {#}; python/run-tests --testnames pyspark.sql.tests.test_udf'
```

Closes apache#34369 from ankurdave/SPARK-37089.

Authored-by: Ankur Dave <[email protected]>
Signed-off-by: Hyukjin Kwon <[email protected]>
(cherry picked from commit 1fc1d07)
Signed-off-by: Hyukjin Kwon <[email protected]>
catalinii pushed a commit to lyft/spark that referenced this pull request Mar 4, 2022
…ner lazily

### What changes were proposed in this pull request?

The previous PR apache#34245 assumed task completion listeners are registered bottom-up. `ParquetFileFormat#buildReaderWithPartitionValues()` violates this assumption by registering a task completion listener to close its output iterator lazily. Since task completion listeners are executed in reverse order of registration, this listener always runs before other listeners. When the downstream operator contains a Python UDF and the off-heap vectorized Parquet reader is enabled, this results in a use-after-free that causes a segfault.

The fix is to close the output iterator using FileScanRDD's task completion listener.

### Why are the changes needed?

Without this PR, the Python tests introduced in apache#34245 are flaky ([see details in thread](apache#34245 (comment))). They intermittently fail with a segfault.

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

Repeatedly ran one of the Python tests introduced in apache#34245 using the commands below. Previously, the test was flaky and failed after about 50 runs. With this PR, the test has not failed after 1000 runs.

```sh
./build/sbt -Phive clean package && ./build/sbt test:compile
seq 1000 | parallel -j 8 --halt now,fail=1 'echo {#}; python/run-tests --testnames pyspark.sql.tests.test_udf'
```

Closes apache#34369 from ankurdave/SPARK-37089.

Authored-by: Ankur Dave <[email protected]>
Signed-off-by: Hyukjin Kwon <[email protected]>
(cherry picked from commit 1fc1d07)
Signed-off-by: Hyukjin Kwon <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants