andygrove commented on code in PR #6900:
URL: https://github.com/apache/datafusion-comet/pull/6900#discussion_r4274400171


##########
spark/src/main/scala/org/apache/spark/sql/comet/util/Utils.scala:
##########
@@ -267,9 +267,15 @@ object Utils extends CometTypeShim with Logging {
       }
       val provider = batchProviderOpt.getOrElse(dictionaryProvider)
 
-      val writer = new ArrowStreamWriter(root, provider, 
Channels.newChannel(out))
+      // VectorSchemaRootAppender, which coalesces broadcast batches, assumes 
offsets that start
+      // at zero, and Arrow Java writes the bytes before the first offset too. 
A slice of the root
+      // rebases the offsets to zero and leaves those bytes out.
+      val written =
+        if (fieldVectors.forall(offsetsStartAtZero)) root else root.slice(0, 
root.getRowCount)
+      val writer = new ArrowStreamWriter(written, provider, 
Channels.newChannel(out))
       writer.start()
       writer.writeBatch()
+      if (written ne root) written.close()

Review Comment:
   If `writer.start()` or `writer.writeBatch()` throws, `written` never gets 
closed. Before this change that was harmless, because the batch's vectors are 
released when `CometExecIterator` closes the batch. But `splitAndTransfer` 
takes its own reference on the validity and data buffers it shares with the 
batch, so an unclosed slice keeps them alive after the batch is closed. For a 
batch imported from native, that pins the native buffers for the life of the 
executor.
   
   It's reachable when a broadcast is cancelled. Broadcast jobs run with 
`interruptOnCancel = true`, and the channel from `Channels.newChannel` is 
interruptible, so a killed task throws `ClosedByInterruptException` on its next 
write. The shuffle reader only checks for a kill when native pulls the next 
block, which an aggregate emitting its output slices no longer does. I 
reproduced it in `UtilsSuite` by calling `Thread.currentThread().interrupt()` 
before serializing a sliced vector. After closing the batch, its allocator 
still held 49,280 bytes, against 0 for an unsliced vector.
   
   Could we close the slice in a `finally`, along these lines?
   
   ```scala
   try {
     val writer = new ArrowStreamWriter(written, provider, 
Channels.newChannel(out))
     writer.start()
     writer.writeBatch()
     root.clear()
     writer.close()
   } finally {
     if (written ne root) written.close()
   }
   ```
   
   That brought it to 0 for me, and `UtilsSuite` and the new join test still 
passed. A test that sets the interrupt flag and checks that a child allocator 
drains to zero after the batch closes would keep it that way.



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