László Bodor created HIVE-30065:
-----------------------------------

             Summary:  EncodedReaderImpl.putFileData batch calls can leak cache 
locks on partial failure
                 Key: HIVE-30065
                 URL: https://issues.apache.org/jira/browse/HIVE-30065
             Project: Hive
          Issue Type: Bug
            Reporter: László Bodor


{{EncodedReaderImpl}} calls {{cacheWrapper.putFileData(fileKey, cacheKeys, 
targetBuffers, baseOffset, tag)}} with the full batch in one shot at two sites:

* [EncodedReaderImpl.java:1071 -- prepareRangesForCompressedRead (uncompressed 
CB 
put)|https://github.com/apache/hive/blob/4cf07f490af49d0e0e019d07c45b629ed8802136/ql/src/java/org/apache/hadoop/hive/ql/io/orc/encoded/EncodedReaderImpl.java#L1071-L1074]
* [EncodedReaderImpl.java:1381 -- preReadUncompressedStreams (pre-read 
uncompressed 
put)|https://github.com/apache/hive/blob/4cf07f490af49d0e0e019d07c45b629ed8802136/ql/src/java/org/apache/hadoop/hive/ql/io/orc/encoded/EncodedReaderImpl.java#L1380-L1384]

{{LowLevelCacheImpl.putFileData}} processes the array one entry at a time and 
can throw {{RuntimeException}} mid-array -- the [length-mismatch guard at 
LowLevelCacheImpl.java:360|https://github.com/apache/hive/blob/4cf07f490af49d0e0e019d07c45b629ed8802136/llap-server/src/java/org/apache/hadoop/hive/llap/cache/LowLevelCacheImpl.java#L360]
 ({{"Found a block with different length at the same offset..."}}) is the 
concrete case. When it throws at index {{k}}:

* Entries {{0..k-1}} were already inserted and locked in the sub-cache.
* The caller unwinds without running 
[processCacheCollisions|https://github.com/apache/hive/blob/4cf07f490af49d0e0e019d07c45b629ed8802136/ql/src/java/org/apache/hadoop/hive/ql/io/orc/encoded/EncodedReaderImpl.java#L1545],
 so the swap-out ({{allocator.deallocate}} our fresh buffer, adopt the cache's) 
never happens.
* Ownership of the already-inserted buffers is now split-brain: the cache is 
holding a lock on them, and the caller still treats them as raw allocations, 
deallocating them on cleanup. Classic use-after-free on the cache side.

The Parquet native cache reader has the same shape and was fixed on HIVE-30059 
by inserting one range at a time so ownership flips atomically per entry -- see 
{{ParquetEncodedDataReader.putColumn}} in [PR 
#6793|https://github.com/apache/hive/pull/6793] for the pattern (one-entry 
{{putFileData}} loop, {{part.owned = true}} set per successful insert, cleanup 
routes each part correctly on a mid-run throw).

The length-mismatch throw is normally an invariant violation (matching ranges 
must have matching sizes), so this is latent rather than routinely-hit -- but 
it's a real hazard that survives any future path that widens what 
{{putFileData}} may reject mid-batch (compression stats mismatch, checksum, 
quota, etc.).

h3. Proposed fix

Convert both call sites to one-entry-at-a-time {{putFileData}} calls, mirroring 
{{ParquetEncodedDataReader.putColumn}}, and fold the corresponding 
{{processCacheCollisions}} handling into the per-entry loop. Alternative: make 
[LowLevelCacheImpl.putFileData|https://github.com/apache/hive/blob/4cf07f490af49d0e0e019d07c45b629ed8802136/llap-server/src/java/org/apache/hadoop/hive/llap/cache/LowLevelCacheImpl.java#L319]
 transactional (roll back the entries it already inserted before throwing) -- 
probably simpler and fixes ORC and Parquet uniformly, but changes a 
widely-shared contract.

h3. Related

* HIVE-30059
* [PR #6793|https://github.com/apache/hive/pull/6793]



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to