Skip to content

Commit 0444899

Browse files
fix: if the max ordinal for a map value is zero then reserve 1 bit for it instead of nothing (#713)
* fix: if the max ordinal for a map value is zero then reserve 1 bit for it instead of nothing * (contd.) fix: if the max ordinal for a map value is zero then reserve 1 bit for it instead of nothing * add consumer code to account for the bad scenario to allow to exit out of bad blobs --------- Co-authored-by: Sunjeet Singh <[email protected]>
1 parent 3f11f46 commit 0444899

File tree

2 files changed

+18
-5
lines changed

2 files changed

+18
-5
lines changed

hollow/src/main/java/com/netflix/hollow/core/read/engine/map/HollowMapDeltaApplicator.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,14 @@ public void applyDelta() {
6161
target.bitsPerMapPointer = delta.bitsPerMapPointer;
6262
target.bitsPerMapSizeValue = delta.bitsPerMapSizeValue;
6363
target.bitsPerKeyElement = delta.bitsPerKeyElement;
64-
target.bitsPerValueElement = delta.bitsPerValueElement;
64+
// Prior to Jan 2025, the producer was able to generate blobs where it reserved 0 bits for the Map value
65+
// element when all keys stored the same record and that record's ordinal was 0. In this case, when reading the
66+
// Map value of the last bucket, the code would trigger ArrayIndexOutOfBoundsException since there was no space
67+
// allocated for the value element. This is a workaround to avoid the exception when transitioning out of one
68+
// of these bad blobs.
69+
target.bitsPerValueElement = delta.bitsPerValueElement == 0 ? 1 : delta.bitsPerValueElement;
6570
target.bitsPerFixedLengthMapPortion = delta.bitsPerFixedLengthMapPortion;
66-
target.bitsPerMapEntry = delta.bitsPerMapEntry;
71+
target.bitsPerMapEntry = target.bitsPerKeyElement + target.bitsPerValueElement;
6772
target.emptyBucketKeyValue = delta.emptyBucketKeyValue;
6873
target.totalNumberOfBuckets = delta.totalNumberOfBuckets;
6974

@@ -143,7 +148,15 @@ private void mergeOrdinal(int ordinal) {
143148
if(!removeData) {
144149
for(long bucketIdx=currentFromStateStartBucket; bucketIdx<fromDataEndBucket; bucketIdx++) {
145150
long bucketKey = from.entryData.getElementValue(bucketIdx * from.bitsPerMapEntry, from.bitsPerKeyElement);
146-
long bucketValue = from.entryData.getElementValue(bucketIdx * from.bitsPerMapEntry + from.bitsPerKeyElement, from.bitsPerValueElement);
151+
// Prior to Jan 2025, the producer was able to generate blobs where it reserved 0 bits for the Map value
152+
// element when all keys stored the same record and that record's ordinal was 0. In this case, when reading the
153+
// Map value of the last bucket, the code would trigger ArrayIndexOutOfBoundsException since there was no space
154+
// allocated for the value element. This is a workaround to avoid the exception when transitioning out of one
155+
// of these bad blobs.
156+
long bucketValue =
157+
from.bitsPerValueElement == 0
158+
? 0
159+
: from.entryData.getElementValue(bucketIdx * from.bitsPerMapEntry + from.bitsPerKeyElement, from.bitsPerValueElement);
147160
if(bucketKey == from.emptyBucketKeyValue)
148161
bucketKey = target.emptyBucketKeyValue;
149162
long currentWriteStartBucketBit = currentWriteStartBucket * target.bitsPerMapEntry;

hollow/src/main/java/com/netflix/hollow/core/write/HollowMapTypeWriteState.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private void gatherStatistics() {
134134
}
135135

136136
bitsPerKeyElement = 64 - Long.numberOfLeadingZeros(maxKeyOrdinal + 1);
137-
bitsPerValueElement = 64 - Long.numberOfLeadingZeros(maxValueOrdinal);
137+
bitsPerValueElement = maxValueOrdinal == 0 ? 1 : 64 - Long.numberOfLeadingZeros(maxValueOrdinal);
138138

139139
bitsPerMapSizeValue = 64 - Long.numberOfLeadingZeros(maxMapSize);
140140

@@ -185,7 +185,7 @@ private void calculateNumShards() {
185185
}
186186

187187
long bitsPerKeyElement = 64 - Long.numberOfLeadingZeros(maxKeyOrdinal + 1);
188-
long bitsPerValueElement = 64 - Long.numberOfLeadingZeros(maxValueOrdinal);
188+
long bitsPerValueElement = maxValueOrdinal == 0 ? 1 : 64 - Long.numberOfLeadingZeros(maxValueOrdinal);
189189
long bitsPerMapSizeValue = 64 - Long.numberOfLeadingZeros(maxMapSize);
190190
long bitsPerMapPointer = 64 - Long.numberOfLeadingZeros(totalOfMapBuckets);
191191

0 commit comments

Comments
 (0)