sarutak commented on code in PR #59914:
URL: https://github.com/apache/spark/pull/59914#discussion_r4418425463


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSource.scala:
##########
@@ -468,6 +469,13 @@ object NativeWriterCommitMessage {
 /**
  * Reads a partition of a native data source: each array of the Arrow stream 
exported by the
  * library is checked, and imported into the Arrow vectors that back the 
returned batch.
+ *
+ * A reader can be closed while another thread is in [[next]]: some operators 
read their input on
+ * a thread of their own that the task does not wait for, such as the stdin 
writer thread of
+ * `PipedRDD`, while `DataSourceRDD` closes the reader once the task 
completes. The library must
+ * not get the release of the stream during get_next, and the task must stay 
valid until then, so
+ * [[close]] then leaves the release to [[next]], once get_next returns, 
rather than wait for it
+ * and keep the task from completing. [[next]] returns false once the reader 
is closed.

Review Comment:
   Fixed in d4d5c202ca8 with the suggested wording: a `next()` that the close 
overlaps returns false or throws the error of its read, and a `next()` called 
after the close returns false without calling the library.
   



##########
sql/core/src/test/resources/native-datasource/test_native_datasource.cc:
##########
@@ -790,24 +812,52 @@ int streamGetNext(ArrowArrayStream* stream, ArrowArray* 
out) {
     state->last_error = "the task was killed";
     return ECANCELED;
   }
-  if (state->next >= state->end) {
-    out->release = nullptr;  // The end of the stream.
-    return 0;
+  if (!state->slow_read.empty()) {
+    // The flag covers the slow read, for streamRelease to tell whether it 
comes during get_next.
+    state->in_get_next = true;
+    int result = EIO;
+    try {
+      std::string reading = state->slow_read + ".reading";
+      struct stat info;
+      if (stat(reading.c_str(), &info) != 0) {
+        writeFile(reading, "");
+        std::string go = state->slow_read + ".continue";
+        for (int i = 0; i < 6000 && stat(go.c_str(), &info) != 0; i++) {
+          std::this_thread::sleep_for(std::chrono::milliseconds(10));
+        }
+      }
+      if (state->fail_slow_read) {

Review Comment:
   Fixed in d4d5c202ca8. After the wait, the `slow_read` stream calls 
`is_interrupted` for its task context and writes the answer to 
`<slow_read>.interrupted`. Both cross-thread tests assert that it is `false`, 
and the thread test also asserts it for the failing read. The stream calls the 
`is_interrupted` pointer that it kept when `read` was called, rather than 
reading it from the struct, and passes the task address, which Spark only looks 
up: a context that Spark freed is no longer registered, so the answer is `true` 
without reading freed memory. A first version that read the pointer from the 
struct crashed the forked test JVM under the mutant below, instead of failing.
   
   Verified with the mutant you described: when `close()` calls `task.close()` 
before it checks `reading`, and still defers the stream release, both tests 
fail on this assertion, with `"[tru]e" did not equal "[fals]e"`.
   



-- 
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