Skip to content

Commit

Permalink
Fix off-by-one bug in RecyclerBytesStreamOutput (#95036) (#95043)
Browse files Browse the repository at this point in the history
Fixing off by one bug when seeking to first byte in the next page.
  • Loading branch information
original-brownbear committed Apr 5, 2023
1 parent f7baae9 commit 92239bd
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 4 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/95036.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 95036
summary: Fix off-by-one bug in `RecyclerBytesStreamOutput`
area: Network
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ private void ensureCapacityFromPosition(long newPosition) {
if (newPosition > Integer.MAX_VALUE - (Integer.MAX_VALUE % pageSize)) {
throw new IllegalArgumentException(getClass().getSimpleName() + " cannot hold more than 2GB of data");
}
while (newPosition > currentCapacity) {
while (newPosition > currentCapacity - 1) {
Recycler.V<BytesRef> newPage = recycler.obtain();
assert pageSize == newPage.v().length;
pages.add(newPage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.common.util.PageCacheRecycler;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.BytesRefRecycler;

Expand Down Expand Up @@ -240,12 +241,43 @@ public void testSeek() throws Exception {
int position = 0;
assertEquals(position, out.position());

out.seek(position += 10);
final List<Tuple<Integer, Byte>> writes = new ArrayList<>();
int randomOffset = randomIntBetween(0, PageCacheRecycler.BYTE_PAGE_SIZE);
out.seek(position += randomOffset);
if (randomBoolean()) {
byte random = randomByte();
out.writeByte(random);
writes.add(Tuple.tuple(position, random));
position++;
}
out.seek(position += PageCacheRecycler.BYTE_PAGE_SIZE);
out.seek(position += PageCacheRecycler.BYTE_PAGE_SIZE + 10);
if (randomBoolean()) {
byte random = randomByte();
out.writeByte(random);
writes.add(Tuple.tuple(position, random));
position++;
}
out.seek(position += PageCacheRecycler.BYTE_PAGE_SIZE + randomOffset);
if (randomBoolean()) {
byte random = randomByte();
out.writeByte(random);
writes.add(Tuple.tuple(position, random));
position++;
}
out.seek(position += PageCacheRecycler.BYTE_PAGE_SIZE * 2);
if (randomBoolean()) {
byte random = randomByte();
out.writeByte(random);
writes.add(Tuple.tuple(position, random));
position++;
}
assertEquals(position, out.position());
assertEquals(position, BytesReference.toBytes(out.bytes()).length);

final BytesReference bytesReference = out.bytes();
assertEquals(position, BytesReference.toBytes(bytesReference).length);
for (Tuple<Integer, Byte> write : writes) {
assertEquals((byte) write.v2(), bytesReference.get(write.v1()));
}

IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> out.seek(Integer.MAX_VALUE + 1L));
assertEquals("RecyclerBytesStreamOutput cannot hold more than 2GB of data", iae.getMessage());
Expand Down Expand Up @@ -1002,4 +1034,12 @@ public V<BytesRef> obtain() {
assertThat(pagesAllocated.get(), equalTo(0L));
}
}

public void testSeekToPageBoundary() {
RecyclerBytesStreamOutput out = new RecyclerBytesStreamOutput(recycler);
out.seek(PageCacheRecycler.BYTE_PAGE_SIZE);
byte b = randomByte();
out.writeByte(b);
assertEquals(b, out.bytes().get(PageCacheRecycler.BYTE_PAGE_SIZE));
}
}

0 comments on commit 92239bd

Please sign in to comment.