viirya commented on code in PR #59581:
URL: https://github.com/apache/spark/pull/59581#discussion_r4318668596


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ColumnarArrowEvalPythonEvaluatorFactory.scala:
##########
@@ -339,6 +344,41 @@ private[python] class 
ColumnarArrowEvalPythonEvaluatorFactory(
   }
 }
 
+/**
+ * Hands the input batches to the Python writer. `enqueue` is called on each
+ * batch before the writer gets it, and returns whether the batch should be
+ * released with closeIfFreeable once it has been written: the writer is done
+ * with a batch when it asks for the next one or reaches the end.
+ */
+private[python] class WrittenBatchReleasingIterator(
+    input: Iterator[ColumnarBatch],
+    enqueue: ColumnarBatch => Boolean)

Review Comment:
   Thanks. In this PR both callers release every batch, but #59582, which is 
stacked on this one, uses the `false` case: on the Arrow columnar path, a batch 
whose columns are held is not released once it is written, but by the consumer 
of the output batch. I kept the predicate here so that its signature does not 
change again there. Happy to simplify it here and add it back in #59582 if you 
prefer.
   



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ArrowEvalPythonExec.scala:
##########
@@ -110,10 +110,11 @@ case class ArrowEvalPythonExec(
       // Columnar path: delegate to doExecuteColumnar, flatten to
       // UnsafeRow. ColumnarBatchRow from rowIterator() is NOT
       // UnsafeRow, and downstream operators (e.g., outer
-      // EvalPythonExec) may cast to UnsafeRow.
+      // EvalPythonExec) may cast to UnsafeRow. Like ColumnarToRowExec,
+      // this releases each batch once its rows are consumed: the
+      // batches of the Arrow columnar path carry the input columns.
       doExecuteColumnar().mapPartitionsInternal { batchIter =>
-        val toUnsafe = UnsafeProjection.create(schema)
-        batchIter.flatMap(_.rowIterator().asScala.map(toUnsafe))
+        new ColumnarToRowIterator(batchIter, UnsafeProjection.create(schema))

Review Comment:
   Thanks, added in 2b49870c814:
   
   - `Arrow Python UDF over many batches releases each input batch` runs a 
pandas UDF over 500 batches of fresh Arrow vectors, on a column (the Arrow 
columnar path, flattened by `doExecute` for a row-based parent) and on an 
expression (the row fallback path). It checks the values of every row and that 
every input batch is released before the task completes. On the row fallback 
path at most one batch is unreleased at a time; on the Arrow columnar path the 
writer runs ahead of the results, so the batches it holds are bounded by how 
far ahead it is rather than by one.
   - The test UDFs of `IntegratedUDFTestUtils` only return strings, so for 
struct results `ArrowOutputProcessorSuite` reads a struct column over several 
batches from an Arrow stream through `ArrowOutputProcessorImpl`, and releases 
each output batch with `closeIfFreeable`. It does not depend on whole-stage 
codegen, since the consumer only calls `closeIfFreeable`. With plain 
`ArrowColumnVector`s it fails: closing the struct vector of the root drops its 
children, and loading the next batch fails with `should have as many children 
as in the schema: found 0 expected 2`.
   
   The new tests fail if the result columns are plain `ArrowColumnVector`s, or 
if `ColumnarToRowIterator` stops releasing batches.
   



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