sarutak opened a new pull request, #59914:
URL: https://github.com/apache/spark/pull/59914

   ### What changes were proposed in this pull request?
   
   This PR makes `NativePartitionReader`, the reader of a partition of a native 
data source (SPARK-60101), release the Arrow stream of the library only once 
`get_next` returns, also when another thread closes the reader while `get_next` 
runs.
   
   - `close()` no longer releases the stream, the vectors, the task context and 
the allocator while a thread is in `next()`. It marks the reader as closed, and 
that `next()` releases them once it is done with the batch that `get_next` 
returned: it then returns false, or throws the error of the read. A failure to 
release them there is logged, since the thread that closed the reader no longer 
gets it. `close()` does not wait, so it does not keep the task from completing.
   - `next()` returns false once the reader is closed, so the reader never 
calls a released stream, and `close()` does nothing when it is called again. 
The reading thread may then close the reader again: `DataSourceRDD` closes a 
reader whose `next()` returns false, as well as from the completion listener. 
Before, a second `close()` failed with `NullPointerException: ArrowArrayStream 
is already closed`.
   - The constructor runs under the same lock, so that a `close()` on another 
thread sees the task and the vectors that it created.
   
   The test library gets an option `slow_read`: the first `get_next` waits 
until the test lets it continue, and releasing the stream records whether Spark 
released it during `get_next` or not. With the option `fail` set to 
`slow_read`, `get_next` then fails.
   
   ### Why are the changes needed?
   
   `spark_datasource.h` promises a library that the callbacks of the stream 
never run concurrently, and that the task context passed to `read` stays valid 
until the stream is released. When a task is killed, it also says that Spark 
only releases the stream once `get_next` returns.
   
   Spark did not keep these promises when another thread reads a partition. 
`DataSourceRDD` closes its readers from a task completion listener, and some 
operators read their input on a thread of their own that the task does not wait 
for. For example, `PipedRDD` reads its parent on its stdin writer thread. On 
SPARK-60101 as merged (99a1bf9a837, the same reader as its last PR commit 
1f4048404be), with a library whose `get_next` takes 2 seconds, 
`spark.read.format(...).load().rdd.pipe("true").count()` in the JVM returned 
after 0.5 seconds, and the library got `release` of the stream while its 
`get_next` was still running. Spark then also logged `IllegalStateException: 
Memory was leaked by query`. After the release, Spark frees the 
`SparkDsTaskContext`, which the library may still use in `get_next` through 
`task->is_interrupted`. So a library that follows the header can use freed 
memory, and crash the executor.
   
   `close()` hands the release to the thread in `next()` rather than waiting 
for it, since that thread can be in a `get_next` that waits for data: 
`task->is_interrupted` stays false for a task that completed, so waiting could 
keep the task from completing for as long as `get_next` blocks.
   
   Besides when the library gets the release, a closed reader now reads nothing 
more and can be closed again. As before, a batch that `next()` returned is only 
valid until the reader is closed, so a thread that still uses it after the task 
completed reads closed vectors.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. Native data sources are not released yet (4.4.0).
   
   ### How was this patch tested?
   
   New tests in `NativeDataSourceSuite`, which hold the library in its first 
`get_next` until they let it continue, so that they do not depend on timing:
   - `release a stream only once get_next returns` reads a native data source 
through `rdd.pipe("cat")`, and completes the task while the stdin writer thread 
of `PipedRDD` is in `get_next`. It checks that the stream is not released when 
the task completes, and that the library gets the release after `get_next` 
returns.
   - `close a reader while another thread reads it` closes a reader while 
another thread is in `next()`, and checks the same, that `next()` then returns 
false, or throws when `get_next` fails, and that a closed reader reads nothing 
more and can be closed again.
   
   Without the change in `NativePartitionReader`, both fail with `The stream 
was released: during get_next.` Each of the other parts of the change, except 
running the constructor under the lock and logging a failure to release the 
resources in `next()`, also makes a test fail when it is reverted.
   
   `NativeDataSourceSuite` and `ColumnarDataSourceSuite` pass locally on Java 
22 on macOS, except two tests that are canceled without pandas, and the new 
tests passed in 5 runs.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Kiro CLI
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to