Skip to content

Commit 5df74cc

Browse files
committed
Replace Math.toIntExact with toIntBytes (#61604)
We convert longs to ints using `Math.toIntExact` in places where we're sure there will be no overflow, but this doesn't explain the intent of these conversions very well. This commit introduces a dedicated method for these conversions, and adds an assertion that we never overflow.
1 parent e14d9c9 commit 5df74cc

File tree

14 files changed

+99
-51
lines changed

14 files changed

+99
-51
lines changed

server/src/main/java/org/elasticsearch/common/unit/ByteSizeUnit.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,22 @@ static long x(long d, long m, long over) {
276276
return d * m;
277277
}
278278

279+
/**
280+
* Convert to an {@code int} number of bytes. Callers are expected to be certain this will not overflow.
281+
* @throws IllegalArgumentException on overflow, unless assertions are enabled in which case it throws an {@link AssertionError}.
282+
* @return The number of bytes represented as an {@code int}.
283+
*/
284+
public final int toIntBytes(long size) {
285+
final long l = toBytes(size);
286+
final int i = (int) l;
287+
if (i != l) {
288+
final String message = "could not convert [" + size + " " + this + "] to an int";
289+
assert false : message;
290+
throw new IllegalArgumentException(message);
291+
}
292+
return i;
293+
}
294+
279295
public abstract long toBytes(long size);
280296

281297
public abstract long toKB(long size);

server/src/test/java/org/elasticsearch/common/unit/ByteSizeValueTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ public void testSimple() {
5252
assertThat(ByteSizeUnit.PB.toPB(10), is(new ByteSizeValue(10, ByteSizeUnit.PB).getPb()));
5353
}
5454

55+
public void testToIntBytes() {
56+
assertThat(ByteSizeUnit.BYTES.toIntBytes(4), equalTo(4));
57+
assertThat(ByteSizeUnit.KB.toIntBytes(4), equalTo(4096));
58+
assertThat(expectThrows(AssertionError.class, () -> ByteSizeUnit.GB.toIntBytes(4)).getMessage(),
59+
containsString("could not convert [4 GB] to an int"));
60+
}
61+
5562
public void testEquality() {
5663
String[] equalValues = new String[]{"1GB", "1024MB", "1048576KB", "1073741824B"};
5764
ByteSizeValue value1 = ByteSizeValue.parseBytesSizeValue(randomFrom(equalValues), "equalTest");

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsConstants.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.elasticsearch.Build;
99
import org.elasticsearch.common.settings.Settings;
10+
import org.elasticsearch.common.unit.ByteSizeUnit;
1011

1112
import static org.elasticsearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;
1213

@@ -44,4 +45,15 @@ public static boolean isSearchableSnapshotStore(Settings indexSettings) {
4445
public static final String CACHE_PREWARMING_THREAD_POOL_SETTING = "xpack.searchable_snapshots.cache_prewarming_thread_pool";
4546

4647
public static final String SNAPSHOT_BLOB_CACHE_INDEX = ".snapshot-blob-cache";
48+
49+
/**
50+
* We use {@code long} to represent offsets and lengths of files since they may be larger than 2GB, but {@code int} to represent
51+
* offsets and lengths of arrays in memory which are limited to 2GB in size. We quite often need to convert from the file-based world
52+
* of {@code long}s into the memory-based world of {@code int}s, knowing for certain that the result will not overflow. This method
53+
* should be used to clarify that we're doing this.
54+
*/
55+
public static int toIntBytes(long l) {
56+
return ByteSizeUnit.BYTES.toIntBytes(l);
57+
}
58+
4759
}

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/blobstore/cache/BlobStoreCacheService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class BlobStoreCacheService extends AbstractLifecycleComponent implements
5151

5252
private static final Logger logger = LogManager.getLogger(BlobStoreCacheService.class);
5353

54-
public static final int DEFAULT_CACHED_BLOB_SIZE = Math.toIntExact(ByteSizeUnit.KB.toBytes(4L));
54+
public static final int DEFAULT_CACHED_BLOB_SIZE = ByteSizeUnit.KB.toIntBytes(4);
5555

5656
private final ClusterService clusterService;
5757
private final ThreadPool threadPool;

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/index/store/cache/CachedBlobContainerIndexInput.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.common.collect.Tuple;
2525
import org.elasticsearch.common.io.Channels;
2626
import org.elasticsearch.common.lease.Releasable;
27+
import org.elasticsearch.common.unit.ByteSizeUnit;
2728
import org.elasticsearch.index.snapshots.blobstore.BlobStoreIndexShardSnapshot.FileInfo;
2829
import org.elasticsearch.index.snapshots.blobstore.SlicedInputStream;
2930
import org.elasticsearch.index.store.BaseSearchableSnapshotIndexInput;
@@ -45,6 +46,7 @@
4546
import java.util.stream.IntStream;
4647

4748
import static org.elasticsearch.index.store.checksum.ChecksumBlobContainerIndexInput.checksumToBytesArray;
49+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.toIntBytes;
4850

4951
public class CachedBlobContainerIndexInput extends BaseSearchableSnapshotIndexInput {
5052

@@ -56,7 +58,7 @@ public class CachedBlobContainerIndexInput extends BaseSearchableSnapshotIndexIn
5658
public static final IOContext CACHE_WARMING_CONTEXT = new IOContext();
5759

5860
private static final Logger logger = LogManager.getLogger(CachedBlobContainerIndexInput.class);
59-
private static final int COPY_BUFFER_SIZE = 8192;
61+
private static final int COPY_BUFFER_SIZE = ByteSizeUnit.KB.toIntBytes(8);
6062

6163
private final SearchableSnapshotDirectory directory;
6264
private final CacheFileReference cacheFileReference;
@@ -219,7 +221,7 @@ protected void readInternal(ByteBuffer b) throws IOException {
219221
);
220222
stats.addIndexCacheBytesRead(cachedBlob.length());
221223

222-
final BytesRefIterator cachedBytesIterator = cachedBlob.bytes().slice(Math.toIntExact(position), length).iterator();
224+
final BytesRefIterator cachedBytesIterator = cachedBlob.bytes().slice(toIntBytes(position), length).iterator();
223225
BytesRef bytesRef;
224226
while ((bytesRef = cachedBytesIterator.next()) != null) {
225227
b.put(bytesRef.bytes, bytesRef.offset, bytesRef.length);
@@ -235,7 +237,7 @@ protected void readInternal(ByteBuffer b) throws IOException {
235237
(channel, from, to, progressUpdater) -> {
236238
final long startTimeNanos = stats.currentTimeNanos();
237239
final BytesRefIterator iterator = cachedBlob.bytes()
238-
.slice(Math.toIntExact(from - cachedBlob.from()), Math.toIntExact(to - from))
240+
.slice(toIntBytes(from - cachedBlob.from()), toIntBytes(to - from))
239241
.iterator();
240242
long writePosition = from;
241243
BytesRef current;
@@ -298,7 +300,7 @@ protected void readInternal(ByteBuffer b) throws IOException {
298300
final int read;
299301
if ((rangeToRead.v2() - rangeToRead.v1()) < b.remaining()) {
300302
final ByteBuffer duplicate = b.duplicate();
301-
duplicate.limit(duplicate.position() + Math.toIntExact(rangeToRead.v2() - rangeToRead.v1()));
303+
duplicate.limit(duplicate.position() + toIntBytes(rangeToRead.v2() - rangeToRead.v1()));
302304
read = readCacheFile(channel, position, duplicate);
303305
assert duplicate.position() <= b.limit();
304306
b.position(duplicate.position());
@@ -311,7 +313,7 @@ protected void readInternal(ByteBuffer b) throws IOException {
311313
if (indexCacheMiss != null) {
312314
final Releasable onCacheFillComplete = stats.addIndexCacheFill();
313315
final CompletableFuture<Integer> readFuture = cacheFile.readIfAvailableOrPending(indexCacheMiss, channel -> {
314-
final int indexCacheMissLength = Math.toIntExact(indexCacheMiss.v2() - indexCacheMiss.v1());
316+
final int indexCacheMissLength = toIntBytes(indexCacheMiss.v2() - indexCacheMiss.v1());
315317

316318
// We assume that we only cache small portions of blobs so that we do not need to:
317319
// - use a BigArrays for allocation
@@ -373,7 +375,7 @@ private int readDirectlyIfAlreadyClosed(long position, ByteBuffer b, Exception e
373375
try {
374376
// cache file was evicted during the range fetching, read bytes directly from blob container
375377
final long length = b.remaining();
376-
final byte[] copyBuffer = new byte[Math.toIntExact(Math.min(COPY_BUFFER_SIZE, length))];
378+
final byte[] copyBuffer = new byte[toIntBytes(Math.min(COPY_BUFFER_SIZE, length))];
377379
logger.trace(
378380
() -> new ParameterizedMessage(
379381
"direct reading of range [{}-{}] for cache file [{}]",
@@ -481,7 +483,7 @@ public void prefetchPart(final int part) throws IOException {
481483

482484
final FileChannel fc = cacheFile.getChannel();
483485
assert assertFileChannelOpen(fc);
484-
final byte[] copyBuffer = new byte[Math.toIntExact(Math.min(COPY_BUFFER_SIZE, rangeLength))];
486+
final byte[] copyBuffer = new byte[toIntBytes(Math.min(COPY_BUFFER_SIZE, rangeLength))];
485487

486488
long totalBytesRead = 0L;
487489
final AtomicLong totalBytesWritten = new AtomicLong();
@@ -507,8 +509,8 @@ public void prefetchPart(final int part) throws IOException {
507509
(channel, start, end, progressUpdater) -> {
508510
final ByteBuffer byteBuffer = ByteBuffer.wrap(
509511
copyBuffer,
510-
Math.toIntExact(start - readStart),
511-
Math.toIntExact(end - start)
512+
toIntBytes(start - readStart),
513+
toIntBytes(end - start)
512514
);
513515
final int writtenBytes = positionalWrite(channel, start, byteBuffer);
514516
logger.trace(
@@ -557,7 +559,7 @@ private static int readSafe(
557559
long remaining,
558560
CacheFileReference cacheFileReference
559561
) throws IOException {
560-
final int len = (remaining < copyBuffer.length) ? Math.toIntExact(remaining) : copyBuffer.length;
562+
final int len = (remaining < copyBuffer.length) ? toIntBytes(remaining) : copyBuffer.length;
561563
final int bytesRead = inputStream.read(copyBuffer, 0, len);
562564
if (bytesRead == -1) {
563565
throw new EOFException(
@@ -616,7 +618,7 @@ private void writeCacheFile(final FileChannel fc, final long start, final long e
616618
assert assertFileChannelOpen(fc);
617619
assert assertCurrentThreadMayWriteCacheFile();
618620
final long length = end - start;
619-
final byte[] copyBuffer = new byte[Math.toIntExact(Math.min(COPY_BUFFER_SIZE, length))];
621+
final byte[] copyBuffer = new byte[toIntBytes(Math.min(COPY_BUFFER_SIZE, length))];
620622
logger.trace(() -> new ParameterizedMessage("writing range [{}-{}] to cache file [{}]", start, end, cacheFileReference));
621623

622624
long bytesCopied = 0L;
@@ -704,7 +706,7 @@ private long getRelativePositionInPart(long position) {
704706
}
705707

706708
private long getLengthOfPart(long part) {
707-
return fileInfo.partBytes(Math.toIntExact(part));
709+
return fileInfo.partBytes(toIntBytes(part));
708710
}
709711

710712
private void ensureValidPosition(long position) {

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/index/store/checksum/ChecksumBlobContainerIndexInput.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import java.util.Arrays;
1818
import java.util.Objects;
1919

20+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.toIntBytes;
21+
2022
/**
2123
* A {@link IndexInput} that can only be used to verify footer checksums.
2224
*/
@@ -108,7 +110,7 @@ private int checksumPositionOrThrow(long pos) {
108110
assert false : "unexpected read or seek at position [" + pos + "] but checksum starts at [" + checksumPosition + ']';
109111
throw new IllegalArgumentException("Can't read or seek before footer checksum");
110112
}
111-
return Math.toIntExact(checksum.length - (length - pos));
113+
return toIntBytes(checksum.length - (length - pos));
112114
}
113115

114116
private static void ensureReadOnceChecksumContext(IOContext context) {

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/index/store/direct/DirectBlobContainerIndexInput.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.Objects;
2626
import java.util.concurrent.atomic.LongAdder;
2727

28+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.toIntBytes;
29+
2830
/**
2931
* A {@link DirectBlobContainerIndexInput} instance corresponds to a single file from a Lucene directory that has been snapshotted. Because
3032
* large Lucene file might be split into multiple parts during the snapshot, {@link DirectBlobContainerIndexInput} requires a
@@ -110,9 +112,9 @@ protected void readInternal(ByteBuffer b) throws IOException {
110112
int currentPart = Math.toIntExact(position / fileInfo.partSize().getBytes());
111113
int remainingBytesInPart;
112114
if (currentPart < (fileInfo.numberOfParts() - 1)) {
113-
remainingBytesInPart = Math.toIntExact(((currentPart + 1L) * fileInfo.partSize().getBytes()) - position);
115+
remainingBytesInPart = toIntBytes(((currentPart + 1L) * fileInfo.partSize().getBytes()) - position);
114116
} else {
115-
remainingBytesInPart = Math.toIntExact(fileInfo.length() - position);
117+
remainingBytesInPart = toIntBytes(fileInfo.length() - position);
116118
}
117119
final int read = Math.min(b.remaining(), remainingBytesInPart);
118120
readInternalBytes(currentPart, position % fileInfo.partSize().getBytes(), b, read);

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/cache/CacheService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.util.Objects;
2525
import java.util.function.Predicate;
2626

27+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.toIntBytes;
28+
2729
/**
2830
* {@link CacheService} maintains a cache entry for all files read from searchable snapshot directories (
2931
* see {@link org.elasticsearch.index.store.SearchableSnapshotDirectory})
@@ -107,7 +109,7 @@ public long getCacheSize() {
107109
* @return the cache range size (in bytes)
108110
*/
109111
public int getRangeSize() {
110-
return Math.toIntExact(rangeSize.getBytes());
112+
return toIntBytes(rangeSize.getBytes());
111113
}
112114

113115
public CacheFile get(final CacheKey cacheKey, final long fileLength, final Path cacheDir) throws Exception {

x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/index/store/IndexInputStatsTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111

1212
import static org.elasticsearch.index.store.IndexInputStats.SEEKING_THRESHOLD;
1313
import static org.elasticsearch.index.store.cache.TestUtils.assertCounter;
14+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.toIntBytes;
1415

1516
public class IndexInputStatsTests extends ESTestCase {
1617

17-
private static LongSupplier FAKE_CLOCK = () -> {
18+
private static final LongSupplier FAKE_CLOCK = () -> {
1819
assert false : "should not be called";
1920
return -1L;
2021
};
@@ -32,7 +33,7 @@ public void testReads() {
3233
for (int i = 0; i < randomIntBetween(1, 50); i++) {
3334
final long currentPosition = randomLongBetween(0L, inputStats.getFileLength() - 1L);
3435
final long previousPosition = randomBoolean() ? currentPosition : randomLongBetween(0L, inputStats.getFileLength() - 1L);
35-
final int bytesRead = randomIntBetween(1, Math.toIntExact(Math.max(1L, inputStats.getFileLength() - currentPosition)));
36+
final int bytesRead = randomIntBetween(1, toIntBytes(Math.max(1L, inputStats.getFileLength() - currentPosition)));
3637

3738
inputStats.incrementBytesRead(previousPosition, currentPosition, bytesRead);
3839

x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/index/store/SearchableSnapshotDirectoryStatsTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.elasticsearch.index.store.cache.TestUtils;
2929
import org.elasticsearch.index.store.cache.TestUtils.NoopBlobStoreCacheService;
3030
import org.elasticsearch.indices.recovery.RecoveryState;
31-
import org.elasticsearch.indices.recovery.RecoveryState;
3231
import org.elasticsearch.indices.recovery.SearchableSnapshotRecoveryState;
3332
import org.elasticsearch.repositories.IndexId;
3433
import org.elasticsearch.snapshots.SnapshotId;
@@ -52,6 +51,7 @@
5251
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_CACHE_ENABLED_SETTING;
5352
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_CACHE_PREWARM_ENABLED_SETTING;
5453
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_UNCACHED_CHUNK_SIZE_SETTING;
54+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.toIntBytes;
5555
import static org.hamcrest.Matchers.allOf;
5656
import static org.hamcrest.Matchers.equalTo;
5757
import static org.hamcrest.Matchers.greaterThan;
@@ -119,7 +119,7 @@ public void testCachedBytesReadsAndWrites() {
119119
final IndexInputStats inputStats = directory.getStats(fileName);
120120
assertThat(inputStats, notNullValue());
121121

122-
final byte[] result = randomReadAndSlice(input, Math.toIntExact(length));
122+
final byte[] result = randomReadAndSlice(input, toIntBytes(length));
123123
assertArrayEquals(fileContent, result);
124124

125125
final long cachedBytesWriteCount = TestUtils.numberOfRanges(length, rangeSize.getBytes());
@@ -171,7 +171,7 @@ public void testCachedBytesReadsAndWritesNoCache() {
171171
final IndexInputStats inputStats = directory.getStats(fileName);
172172
assertThat(inputStats, notNullValue());
173173

174-
final byte[] result = randomReadAndSlice(input, Math.toIntExact(length));
174+
final byte[] result = randomReadAndSlice(input, toIntBytes(length));
175175
assertArrayEquals(fileContent, result);
176176

177177
assertThat(inputStats.getCachedBytesWritten(), notNullValue());
@@ -213,7 +213,7 @@ public void testDirectBytesReadsWithCache() {
213213
// read all index input sequentially as it simplifies testing
214214
final byte[] readBuffer = new byte[512];
215215
for (long i = 0L; i < input.length();) {
216-
int size = between(1, Math.toIntExact(Math.min(readBuffer.length, input.length() - input.getFilePointer())));
216+
int size = between(1, toIntBytes(Math.min(readBuffer.length, input.length() - input.getFilePointer())));
217217
input.readBytes(readBuffer, 0, size);
218218
i += size;
219219

@@ -331,7 +331,7 @@ public void testReadBytesContiguously() {
331331

332332
// read the input input sequentially
333333
for (long bytesRead = 0L; bytesRead < input.length();) {
334-
int size = between(1, Math.toIntExact(Math.min(readBuffer.length, input.length() - bytesRead)));
334+
int size = between(1, toIntBytes(Math.min(readBuffer.length, input.length() - bytesRead)));
335335
input.readBytes(readBuffer, 0, size);
336336
bytesRead += size;
337337

@@ -381,7 +381,7 @@ public void testReadBytesNonContiguously() {
381381
input.seek(randomPosition);
382382

383383
final byte[] readBuffer = new byte[512];
384-
int size = between(1, Math.toIntExact(Math.min(readBuffer.length, input.length() - randomPosition)));
384+
int size = between(1, toIntBytes(Math.min(readBuffer.length, input.length() - randomPosition)));
385385
input.readBytes(readBuffer, 0, size);
386386

387387
// BufferedIndexInput tries to read as much bytes as possible

0 commit comments

Comments
 (0)