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

   ### What changes were proposed in this pull request?
   
   Adds `BoundedZstdCompressionCodec`, a subclass of Arrow's 
`ZstdCompressionCodec` that overrides only `doCompress` so that zstd is given 
`compressBound(len)` as its destination capacity, and uses it from 
`ArrowCompressionUtils.createCompressionCodec`. That method creates the 
write-side codec for the Arrow IPC batches written for Python and Spark Connect 
when `spark.sql.execution.arrow.compression.codec=zstd`.
   
   `ArrowCachedBatchSerializer.createCompressionCodec` was a second copy of the 
same codec factory; it now delegates to 
`ArrowCompressionUtils.createCompressionCodec` so the workaround lives in one 
place.
   
   Decompression is inherited from Arrow unchanged. The class carries a note 
that it can be removed once Spark is on Arrow 20.0.0.
   
   ### Why are the changes needed?
   
   In Arrow 18.3.0 and 19.0.0, `ZstdCompressionCodec.doCompress` allocates 
`compressBound(len) + 8` bytes, points zstd 8 bytes into that buffer (past the 
uncompressed-length prefix), but still passes `compressBound(len) + 8` as the 
destination capacity. zstd only guarantees that it stays within the capacity it 
is given, and on a block that does not compress it can fill all of it, so it 
can write up to 8 bytes past the end of the buffer. Arrow's allocator rounds 
requests up to a power of two, which hides the overrun except when the request 
lands within 8 bytes below a power of two; then the overrun lands in whatever 
follows the buffer, with no error (with Native Memory Tracking on, the JVM 
aborts when the buffer is freed).
   
   This is fixed upstream in apache/arrow-java#1119 (Arrow 20.0.0). Spark 
cannot take a major Arrow bump on the 4.x branches, so this works around it on 
the Spark side.
   
   Only sessions that opt in to zstd are affected; the default is `none`.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. With zstd enabled, compressed output is the same; only the capacity 
handed to zstd changes, which removes the out-of-bounds write.
   
   ### How was this patch tested?
   
   Added to `ArrowCompressionUtilsSuite`:
   
   - A test that allocates the codec's output from an allocator that puts a 
sentinel guard directly behind each buffer, compresses random (incompressible) 
inputs of several sizes from 1 byte to 256 KB, and asserts the guard is intact. 
Run against Arrow's stock `ZstdCompressionCodec` it fails (`zstd wrote past the 
end of a 572 byte output buffer (input size 500)`); it passes with 
`BoundedZstdCompressionCodec`.
   - A round-trip test for compressible and incompressible buffers through 
`createCompressionCodec("zstd", ...)`.
   
   Ran `ArrowCompressionUtilsSuite` locally on this tree. 
`ArrowCachedBatchSerializerSuite` passed locally before 
`ArrowCachedBatchSerializer` was changed to delegate to 
`ArrowCompressionUtils`; after that change it is covered by CI.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Sonnet 5.5)
   


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