szetszwo commented on code in PR #11102:
URL: https://github.com/apache/ozone/pull/11102#discussion_r3981696662
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java:
##########
@@ -430,14 +433,130 @@ protected void readChunkDataIntoBuffers(ChunkInfo
readChunkInfo)
}
/**
- * Send RPC call to get the chunk from the container.
+ * Whether this chunk stream can serve positioned reads without holding a
+ * lock. A plain chunk read is a self-contained RPC, so concurrent callers
+ * reading different ranges do not interfere. Overridden by
+ * {@link LocalChunkInputStream} uses positional {@link FileChannel} reads on
+ * the shared block channel, so concurrent callers on different chunks do not
+ * interfere.
*/
- @VisibleForTesting
- protected ByteBuffer[] readChunk(ChunkInfo readChunkInfo)
+ boolean supportsConcurrentPositionedRead() {
+ return true;
+ }
+
+ /**
+ * Stateless positioned read of up to {@code dst.remaining()} bytes starting
+ * at {@code chunkRelativePosition} within this chunk. Unlike the buffered
+ * {@link #read} path, this does not read or mutate any of the instance's
+ * buffer/position state ({@code buffers}, {@code chunkPosition},
+ * {@code bufferOffsetWrtChunkData}, ...), so it is safe to call concurrently
+ * from multiple threads sharing the same stream.
+ *
+ * @param chunkRelativePosition start offset within this chunk
+ * @param dst destination buffer
+ * @return number of bytes copied into {@code dst}, or {@link #EOF} at EOF
+ */
+ int readPositioned(long chunkRelativePosition, ByteBuffer dst)
+ throws IOException {
+ if (supportsConcurrentPositionedRead()) {
+ return doPositionedRead(chunkRelativePosition, dst);
+ }
+ // Local (short-circuit) reads share a FileChannel cursor; serialize them.
+ synchronized (this) {
+ return doPositionedRead(chunkRelativePosition, dst);
+ }
+ }
+
+ private int doPositionedRead(long chunkRelativePosition, ByteBuffer dst)
throws IOException {
+ if (chunkRelativePosition < 0 || chunkRelativePosition >= length) {
+ return EOF;
+ }
+ final int toRead =
+ (int) Math.min(dst.remaining(), length - chunkRelativePosition);
+ if (toRead == 0) {
+ return 0;
+ }
+
+ final long adjustedOffset;
+ final long adjustedLen;
+ if (verifyChecksum) {
+ Pair<Long, Long> boundaries =
+ computeChecksumBoundaries(chunkRelativePosition, toRead);
+ adjustedOffset = boundaries.getLeft();
+ adjustedLen = boundaries.getRight();
+ } else {
+ adjustedOffset = chunkRelativePosition;
+ adjustedLen = toRead;
+ }
+
+ final ChunkInfo readChunkInfo = ChunkInfo.newBuilder(chunkInfo)
+ .setOffset(chunkInfo.getOffset() + adjustedOffset)
+ .setLen(adjustedLen)
+ .build();
Review Comment:
Let's add a getChunkInfo(..) method and use it here and also
readChunkFromContainer(..):
```diff
+++
b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java
@@ -381,29 +381,10 @@ private synchronized void readChunkFromContainer(int
len) throws IOException {
// successful read in adjustBufferPosition()
storePosition();
- long adjustedBuffersOffset, adjustedBuffersLen;
- if (verifyChecksum) {
- // Adjust the chunk offset and length to include required checksum
- // boundaries
- Pair<Long, Long> adjustedOffsetAndLength =
- computeChecksumBoundaries(startByteIndex, len);
- adjustedBuffersOffset = adjustedOffsetAndLength.getLeft();
- adjustedBuffersLen = adjustedOffsetAndLength.getRight();
- } else {
- // Read from the startByteIndex
- adjustedBuffersOffset = startByteIndex;
- adjustedBuffersLen = len;
- }
-
- // Adjust the chunkInfo so that only the required bytes are read from
- // the chunk.
- final ChunkInfo adjustedChunkInfo = ChunkInfo.newBuilder(chunkInfo)
- .setOffset(chunkInfo.getOffset() + adjustedBuffersOffset)
- .setLen(adjustedBuffersLen)
- .build();
+ final ChunkInfo adjustedChunkInfo = getChunkInfo(startByteIndex, len);
readChunkDataIntoBuffers(adjustedChunkInfo);
- bufferOffsetWrtChunkData = adjustedBuffersOffset;
+ bufferOffsetWrtChunkData = adjustedChunkInfo.getOffset() -
chunkInfo.getOffset();
// If the stream was seeked to position before, then the buffer
// position should be adjusted as the reads happen at checksum
boundaries.
```
```diff
@@ -415,6 +396,24 @@ private synchronized void readChunkFromContainer(int
len) throws IOException {
adjustBufferPosition(startByteIndex - bufferOffsetWrtChunkData);
}
+ ChunkInfo getChunkInfo(long chunkRelativePosition, int toRead) {
+ final long adjustedOffset;
+ final long adjustedLen;
+ if (verifyChecksum) {
+ Pair<Long, Long> boundaries =
computeChecksumBoundaries(chunkRelativePosition, toRead);
+ adjustedOffset = boundaries.getLeft();
+ adjustedLen = boundaries.getRight();
+ } else {
+ adjustedOffset = chunkRelativePosition;
+ adjustedLen = toRead;
+ }
+
+ return ChunkInfo.newBuilder(chunkInfo)
+ .setOffset(chunkInfo.getOffset() + adjustedOffset)
+ .setLen(adjustedLen)
+ .build();
+ }
+
protected void readChunkDataIntoBuffers(ChunkInfo readChunkInfo)
throws IOException {
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/MultipartInputStream.java:
##########
@@ -219,6 +234,60 @@ int readImpl(InputStream inputStream) throws IOException {
return true;
}
+ /**
+ * Stateless positioned read for the replicated (Ratis) path. Routes the read
+ * across the part {@link BlockInputStream}s using the immutable
+ * {@link #partOffsets} without seeking or mutating the shared cursor, so
+ * concurrent positioned reads run independently. Returns {@code false} (so
+ * the caller can fall back) when any part is not a {@link BlockInputStream},
+ * e.g. erasure coded parts.
+ */
+ private boolean readFullyStateless(long position, ByteBuffer buffer)
+ throws IOException {
+ if (!buffer.hasRemaining()) {
+ return true;
+ }
+ if (!statelessPositionedReadSupported) {
+ return false;
+ }
+
+ long pos = position;
+ int bytesRead = 0;
+ while (buffer.hasRemaining()) {
+ if (pos < 0 || pos >= length) {
+ if (bytesRead > 0) {
+ return true;
+ }
+ throw new EOFException("EOF encountered at pos: " + pos +
+ " for key: " + key);
+ }
+ int idx = partIndexForPosition(pos);
+ BlockInputStream part = (BlockInputStream) partStreams.get(idx);
+ long partPos = pos - partOffsets[idx];
+ int n = part.readPositioned(partPos, buffer);
+ if (n <= 0) {
+ if (bytesRead > 0) {
+ return true;
+ }
+ throw new EOFException("EOF encountered at pos: " + pos +
+ " for key: " + key);
+ }
+ bytesRead += n;
+ pos += n;
+ }
+ return true;
+ }
+
+ private int partIndexForPosition(long pos) {
+ int idx = Arrays.binarySearch(partOffsets, pos);
+ if (idx < 0) {
+ // binarySearch returns -insertionPoint - 1; the containing part is
+ // insertionPoint - 1.
+ idx = -idx - 2;
+ }
+ return idx;
+ }
Review Comment:
Let's make it static and reuse it in BlockInputStream:
```java
static int binarySearchOffsetIndex(long[] offsets, long pos) {
final int idx = Arrays.binarySearch(offsets, pos);
if (idx > 0) {
return idx;
}
// binarySearch returns n = -insertionPoint - 1;
// insertionPoint is -n - 1
// the containing index is insertionPoint - 1.
return -idx - 2;
}
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java:
##########
@@ -430,14 +433,130 @@ protected void readChunkDataIntoBuffers(ChunkInfo
readChunkInfo)
}
/**
- * Send RPC call to get the chunk from the container.
+ * Whether this chunk stream can serve positioned reads without holding a
+ * lock. A plain chunk read is a self-contained RPC, so concurrent callers
+ * reading different ranges do not interfere. Overridden by
+ * {@link LocalChunkInputStream} uses positional {@link FileChannel} reads on
+ * the shared block channel, so concurrent callers on different chunks do not
+ * interfere.
*/
- @VisibleForTesting
- protected ByteBuffer[] readChunk(ChunkInfo readChunkInfo)
+ boolean supportsConcurrentPositionedRead() {
+ return true;
+ }
Review Comment:
supportsConcurrentPositionedRead() always returns true. Let's remove it:
```diff
@@ -432,18 +431,6 @@ protected void readChunkDataIntoBuffers(ChunkInfo
readChunkInfo)
allocated = true;
}
- /**
- * Whether this chunk stream can serve positioned reads without holding a
- * lock. A plain chunk read is a self-contained RPC, so concurrent callers
- * reading different ranges do not interfere. Overridden by
- * {@link LocalChunkInputStream} uses positional {@link FileChannel}
reads on
- * the shared block channel, so concurrent callers on different chunks do
not
- * interfere.
- */
- boolean supportsConcurrentPositionedRead() {
- return true;
- }
-
/**
* Stateless positioned read of up to {@code dst.remaining()} bytes
starting
* at {@code chunkRelativePosition} within this chunk. Unlike the buffered
```
```diff
@@ -456,19 +443,7 @@ boolean supportsConcurrentPositionedRead() {
* @param dst destination buffer
* @return number of bytes copied into {@code dst}, or {@link #EOF} at EOF
*/
- int readPositioned(long chunkRelativePosition, ByteBuffer dst)
- throws IOException {
- if (supportsConcurrentPositionedRead()) {
- return doPositionedRead(chunkRelativePosition, dst);
- }
- // Local (short-circuit) reads share a FileChannel cursor; serialize
them.
- synchronized (this) {
- return doPositionedRead(chunkRelativePosition, dst);
- }
- }
-
- private int doPositionedRead(long chunkRelativePosition, ByteBuffer dst)
- throws IOException {
+ synchronized int readPositioned(long chunkRelativePosition, ByteBuffer
dst) throws IOException {
if (chunkRelativePosition < 0 || chunkRelativePosition >= length) {
return EOF;
}
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java:
##########
@@ -486,6 +487,116 @@ protected synchronized int
readWithStrategy(ByteReaderStrategy strategy)
* 2. chunkStream[2] will be seeked to position 10
* (= 90 - chunkOffset[2] (= 80)).
*/
+ /**
+ * Stateless positioned read across this block's chunks. Fills up to
+ * {@code dst.remaining()} bytes starting from {@code blockRelativePosition}
+ * without mutating this stream's cursor ({@code chunkIndex},
+ * {@code blockPosition}) or the sequential chunk streams' buffered state.
+ * Each covering chunk is read through an ephemeral {@link ChunkInputStream}
+ * closed as soon as its bytes have been copied.
+ *
+ * @return bytes copied into {@code dst}, or {@link #EOF} at EOF
+ */
+ int readPositioned(long blockRelativePosition, ByteBuffer dst)
+ throws IOException {
Review Comment:
Check blockRelativePosition first. BTW, please don't use short lines:
```java
int readPositioned(long blockRelativePosition, ByteBuffer dst) throws
IOException {
if (blockRelativePosition < 0) {
return EOF;
}
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java:
##########
@@ -306,13 +306,16 @@ private void updateDatanodeBlockId(Pipeline pipeline)
throws IOException {
/**
* Acquire new client if previous one was released.
+ *
+ * @return the held client after ensuring it is acquired
*/
- protected synchronized void acquireClient() throws IOException {
+ protected synchronized XceiverClientSpi acquireClient() throws IOException {
Review Comment:
Change it to private and remove it from LocalChunkInputStream and
DummyChunkInputStream.
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java:
##########
@@ -430,14 +433,130 @@ protected void readChunkDataIntoBuffers(ChunkInfo
readChunkInfo)
}
/**
- * Send RPC call to get the chunk from the container.
+ * Whether this chunk stream can serve positioned reads without holding a
+ * lock. A plain chunk read is a self-contained RPC, so concurrent callers
+ * reading different ranges do not interfere. Overridden by
+ * {@link LocalChunkInputStream} uses positional {@link FileChannel} reads on
+ * the shared block channel, so concurrent callers on different chunks do not
+ * interfere.
*/
- @VisibleForTesting
- protected ByteBuffer[] readChunk(ChunkInfo readChunkInfo)
+ boolean supportsConcurrentPositionedRead() {
+ return true;
+ }
+
+ /**
+ * Stateless positioned read of up to {@code dst.remaining()} bytes starting
+ * at {@code chunkRelativePosition} within this chunk. Unlike the buffered
+ * {@link #read} path, this does not read or mutate any of the instance's
+ * buffer/position state ({@code buffers}, {@code chunkPosition},
+ * {@code bufferOffsetWrtChunkData}, ...), so it is safe to call concurrently
+ * from multiple threads sharing the same stream.
+ *
+ * @param chunkRelativePosition start offset within this chunk
+ * @param dst destination buffer
+ * @return number of bytes copied into {@code dst}, or {@link #EOF} at EOF
+ */
+ int readPositioned(long chunkRelativePosition, ByteBuffer dst)
+ throws IOException {
+ if (supportsConcurrentPositionedRead()) {
+ return doPositionedRead(chunkRelativePosition, dst);
+ }
+ // Local (short-circuit) reads share a FileChannel cursor; serialize them.
+ synchronized (this) {
+ return doPositionedRead(chunkRelativePosition, dst);
+ }
+ }
+
+ private int doPositionedRead(long chunkRelativePosition, ByteBuffer dst)
throws IOException {
+ if (chunkRelativePosition < 0 || chunkRelativePosition >= length) {
+ return EOF;
+ }
+ final int toRead =
+ (int) Math.min(dst.remaining(), length - chunkRelativePosition);
+ if (toRead == 0) {
+ return 0;
+ }
+
+ final long adjustedOffset;
+ final long adjustedLen;
+ if (verifyChecksum) {
+ Pair<Long, Long> boundaries =
+ computeChecksumBoundaries(chunkRelativePosition, toRead);
+ adjustedOffset = boundaries.getLeft();
+ adjustedLen = boundaries.getRight();
+ } else {
+ adjustedOffset = chunkRelativePosition;
+ adjustedLen = toRead;
+ }
+
+ final ChunkInfo readChunkInfo = ChunkInfo.newBuilder(chunkInfo)
+ .setOffset(chunkInfo.getOffset() + adjustedOffset)
+ .setLen(adjustedLen)
+ .build();
+
+ // Capture the client reference under the acquireClient() lock so that a
concurrent
+ // releaseClient() (e.g. from the sequential read's handleReadError or
unbuffer) cannot
+ // null the xceiverClient field between acquisition and use.
+ final XceiverClientSpi client = acquireClient();
+ final ByteBuffer[] readBuffers = readChunk(client, readChunkInfo);
+ return copyRange(readBuffers, chunkRelativePosition - adjustedOffset,
+ toRead, dst);
+ }
+
+ /**
+ * Copy {@code toCopy} bytes from {@code src} buffers, skipping the first
+ * {@code skip} bytes, into {@code dst}. Operates on duplicates so the source
+ * buffers' positions are left untouched.
+ */
+ private static int copyRange(ByteBuffer[] src, long skip, int toCopy,
+ ByteBuffer dst) {
+ long remainingSkip = skip;
+ int copied = 0;
+ for (ByteBuffer buffer : src) {
+ if (copied >= toCopy) {
+ break;
+ }
+ ByteBuffer dup = buffer.duplicate();
+ if (remainingSkip > 0) {
+ int skipHere = (int) Math.min(remainingSkip, dup.remaining());
+ dup.position(dup.position() + skipHere);
+ remainingSkip -= skipHere;
+ if (!dup.hasRemaining()) {
+ continue;
+ }
+ }
Review Comment:
- duplicate() is not needed
- We may simplify it by first checking remainingSkip >= buffer.remaining().
```java
if (remainingSkip > 0) {
if (remainingSkip >= buffer.remaining()) {
remainingSkip -= buffer.remaining();
continue;
} else {
buffer.position(Math.toIntExact(buffer.position() +
remainingSkip));
remainingSkip = 0;
}
}
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java:
##########
@@ -486,6 +487,116 @@ protected synchronized int
readWithStrategy(ByteReaderStrategy strategy)
* 2. chunkStream[2] will be seeked to position 10
* (= 90 - chunkOffset[2] (= 80)).
*/
+ /**
+ * Stateless positioned read across this block's chunks. Fills up to
+ * {@code dst.remaining()} bytes starting from {@code blockRelativePosition}
+ * without mutating this stream's cursor ({@code chunkIndex},
+ * {@code blockPosition}) or the sequential chunk streams' buffered state.
+ * Each covering chunk is read through an ephemeral {@link ChunkInputStream}
+ * closed as soon as its bytes have been copied.
+ *
+ * @return bytes copied into {@code dst}, or {@link #EOF} at EOF
+ */
+ int readPositioned(long blockRelativePosition, ByteBuffer dst)
+ throws IOException {
+ if (!initialized) {
+ initialize();
+ }
+ final long[] offsets;
+ final BlockData currentBlockData;
+ final long blockLength;
+ synchronized (this) {
+ checkOpen();
+ offsets = chunkOffsets;
+ currentBlockData = blockData;
+ blockLength = length;
+ }
+ if (offsets == null || currentBlockData == null
+ || blockRelativePosition < 0 || blockRelativePosition >= blockLength) {
Review Comment:
- chunkOffsets is an array. We need to copy it:
- Let's move the if inside since we have to make use chunkOffsets is not
null before copying.
```java
synchronized (this) {
checkOpen();
if (chunkOffsets == null || blockData == null || blockRelativePosition
>= length) {
return EOF;
}
offsets = Arrays.copyOf(chunkOffsets, chunkOffsets.length);
currentBlockData = blockData;
blockLength = length;
}
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java:
##########
@@ -486,6 +487,116 @@ protected synchronized int
readWithStrategy(ByteReaderStrategy strategy)
* 2. chunkStream[2] will be seeked to position 10
* (= 90 - chunkOffset[2] (= 80)).
*/
+ /**
+ * Stateless positioned read across this block's chunks. Fills up to
Review Comment:
This part of the code is inserted between seek(..) and seek(..)'s javadoc.
Please keep seek(..)'s javadoc with seek(..).
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java:
##########
@@ -486,6 +487,116 @@ protected synchronized int
readWithStrategy(ByteReaderStrategy strategy)
* 2. chunkStream[2] will be seeked to position 10
* (= 90 - chunkOffset[2] (= 80)).
*/
+ /**
+ * Stateless positioned read across this block's chunks. Fills up to
+ * {@code dst.remaining()} bytes starting from {@code blockRelativePosition}
+ * without mutating this stream's cursor ({@code chunkIndex},
+ * {@code blockPosition}) or the sequential chunk streams' buffered state.
+ * Each covering chunk is read through an ephemeral {@link ChunkInputStream}
+ * closed as soon as its bytes have been copied.
+ *
+ * @return bytes copied into {@code dst}, or {@link #EOF} at EOF
+ */
+ int readPositioned(long blockRelativePosition, ByteBuffer dst)
+ throws IOException {
+ if (!initialized) {
+ initialize();
+ }
+ final long[] offsets;
+ final BlockData currentBlockData;
+ final long blockLength;
+ synchronized (this) {
+ checkOpen();
+ offsets = chunkOffsets;
+ currentBlockData = blockData;
+ blockLength = length;
+ }
+ if (offsets == null || currentBlockData == null
+ || blockRelativePosition < 0 || blockRelativePosition >= blockLength) {
+ return EOF;
+ }
+
+ final List<ChunkInfo> chunkInfos = currentBlockData.getChunksList();
+ int index = Arrays.binarySearch(offsets, blockRelativePosition);
+ if (index < 0) {
+ index = -index - 2;
+ }
+
+ long pos = blockRelativePosition;
+ int totalReadLen = 0;
+ while (dst.hasRemaining() && pos < blockLength && index <
chunkInfos.size()) {
+ final ChunkInfo chunkInfo = chunkInfos.get(index);
+ final long chunkOffset = pos - offsets[index];
+ final long numBytesToRead = Math.min(
+ Math.min(dst.remaining(), chunkInfo.getLen() - chunkOffset),
blockLength - pos);
+ if (numBytesToRead <= 0) {
+ index++;
+ continue;
+ }
+ final int numBytesRead =
+ readChunkAt(chunkInfo, chunkOffset, (int) numBytesToRead, dst);
+ totalReadLen += numBytesRead;
+ pos += numBytesRead;
+ index++;
+ }
+ return totalReadLen == 0 ? EOF : totalReadLen;
+ }
+
+ /**
+ * Read {@code numBytesToRead} bytes starting at {@code chunkOffset} of the
given chunk into {@code dst}
+ * through an ephemeral {@link ChunkInputStream}, retrying like {@link
#readWithStrategy(ByteReaderStrategy)}
+ * but with a retry counter local to this call.
+ */
+ private int readChunkAt(ChunkInfo chunkInfo, long chunkOffset, int
numBytesToRead, ByteBuffer dst)
Review Comment:
Let's rename numBytesToRead to readLength (or toRead) since numBytesToRead
and numBytesRead look similar.
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java:
##########
@@ -430,14 +433,130 @@ protected void readChunkDataIntoBuffers(ChunkInfo
readChunkInfo)
}
/**
- * Send RPC call to get the chunk from the container.
+ * Whether this chunk stream can serve positioned reads without holding a
+ * lock. A plain chunk read is a self-contained RPC, so concurrent callers
+ * reading different ranges do not interfere. Overridden by
+ * {@link LocalChunkInputStream} uses positional {@link FileChannel} reads on
+ * the shared block channel, so concurrent callers on different chunks do not
+ * interfere.
*/
- @VisibleForTesting
- protected ByteBuffer[] readChunk(ChunkInfo readChunkInfo)
+ boolean supportsConcurrentPositionedRead() {
+ return true;
+ }
+
+ /**
+ * Stateless positioned read of up to {@code dst.remaining()} bytes starting
+ * at {@code chunkRelativePosition} within this chunk. Unlike the buffered
+ * {@link #read} path, this does not read or mutate any of the instance's
+ * buffer/position state ({@code buffers}, {@code chunkPosition},
+ * {@code bufferOffsetWrtChunkData}, ...), so it is safe to call concurrently
+ * from multiple threads sharing the same stream.
+ *
+ * @param chunkRelativePosition start offset within this chunk
+ * @param dst destination buffer
+ * @return number of bytes copied into {@code dst}, or {@link #EOF} at EOF
+ */
+ int readPositioned(long chunkRelativePosition, ByteBuffer dst)
+ throws IOException {
+ if (supportsConcurrentPositionedRead()) {
+ return doPositionedRead(chunkRelativePosition, dst);
+ }
+ // Local (short-circuit) reads share a FileChannel cursor; serialize them.
+ synchronized (this) {
+ return doPositionedRead(chunkRelativePosition, dst);
+ }
+ }
+
+ private int doPositionedRead(long chunkRelativePosition, ByteBuffer dst)
throws IOException {
+ if (chunkRelativePosition < 0 || chunkRelativePosition >= length) {
+ return EOF;
+ }
+ final int toRead =
+ (int) Math.min(dst.remaining(), length - chunkRelativePosition);
+ if (toRead == 0) {
+ return 0;
+ }
+
+ final long adjustedOffset;
+ final long adjustedLen;
+ if (verifyChecksum) {
+ Pair<Long, Long> boundaries =
+ computeChecksumBoundaries(chunkRelativePosition, toRead);
+ adjustedOffset = boundaries.getLeft();
+ adjustedLen = boundaries.getRight();
+ } else {
+ adjustedOffset = chunkRelativePosition;
+ adjustedLen = toRead;
+ }
+
+ final ChunkInfo readChunkInfo = ChunkInfo.newBuilder(chunkInfo)
+ .setOffset(chunkInfo.getOffset() + adjustedOffset)
+ .setLen(adjustedLen)
+ .build();
+
+ // Capture the client reference under the acquireClient() lock so that a
concurrent
+ // releaseClient() (e.g. from the sequential read's handleReadError or
unbuffer) cannot
+ // null the xceiverClient field between acquisition and use.
+ final XceiverClientSpi client = acquireClient();
+ final ByteBuffer[] readBuffers = readChunk(client, readChunkInfo);
+ return copyRange(readBuffers, chunkRelativePosition - adjustedOffset,
+ toRead, dst);
+ }
+
+ /**
+ * Copy {@code toCopy} bytes from {@code src} buffers, skipping the first
+ * {@code skip} bytes, into {@code dst}. Operates on duplicates so the source
+ * buffers' positions are left untouched.
+ */
+ private static int copyRange(ByteBuffer[] src, long skip, int toCopy,
+ ByteBuffer dst) {
+ long remainingSkip = skip;
+ int copied = 0;
+ for (ByteBuffer buffer : src) {
+ if (copied >= toCopy) {
+ break;
+ }
+ ByteBuffer dup = buffer.duplicate();
+ if (remainingSkip > 0) {
+ int skipHere = (int) Math.min(remainingSkip, dup.remaining());
+ dup.position(dup.position() + skipHere);
+ remainingSkip -= skipHere;
+ if (!dup.hasRemaining()) {
+ continue;
+ }
+ }
+ int n = Math.min(dup.remaining(), toCopy - copied);
+ if (n <= 0) {
+ continue;
+ }
+ dup.limit(dup.position() + n);
+ dst.put(dup);
+ copied += n;
+ }
+ return copied;
+ }
+
+ /**
+ * Send RPC call to get the chunk from the container using the
sequential-read client held in
+ * {@link #xceiverClient}. Called by the buffered sequential read path via
+ * {@link #readChunkDataIntoBuffers}.
+ */
+ @VisibleForTesting
+ protected ByteBuffer[] readChunk(ChunkInfo readChunkInfo) throws IOException
{
+ return readChunk(xceiverClient, readChunkInfo);
+ }
+ /**
+ * Send RPC call to get the chunk from the container using an explicitly
provided client.
+ * Used by the positioned-read path so callers hold a local reference to the
client
+ * rather than re-reading the shared {@link #xceiverClient} field after the
lock is
+ * released.
+ */
+ protected ByteBuffer[] readChunk(XceiverClientSpi client, ChunkInfo
readChunkInfo)
Review Comment:
- Make it private.
- Reorder the parameters so both readChunk methods start with readChunkInfo.
- Avoid short lines.
```java
/**
* Send RPC call to get the chunk from the container using an explicitly
provided client.
* Used by the positioned-read path so callers hold a local reference to
the client
* rather than re-reading the shared {@link #xceiverClient} field after
the lock is released.
*/
private ByteBuffer[] readChunk(ChunkInfo readChunkInfo, XceiverClientSpi
client) throws IOException {
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/LocalChunkInputStream.java:
##########
@@ -82,12 +88,34 @@ protected ByteBuffer[] readChunk(ChunkInfo readChunkInfo)
int bytesPerChecksum = chunkInfo.getChecksumData().getBytesPerChecksum();
final ByteBuffer[] buffers =
BufferUtils.assignByteBuffers(readChunkInfo.getLen(),
bytesPerChecksum);
- dataIn.position(readChunkInfo.getOffset()).read(buffers);
+ readAtOffset(buffers, readChunkInfo.getOffset());
Arrays.stream(buffers).forEach(ByteBuffer::flip);
validator.accept(Arrays.asList(buffers), readChunkInfo);
return buffers;
}
+ /**
+ * Read into {@code buffers} starting at {@code fileOffset} using positional
+ * {@link FileChannel} reads so concurrent callers on different chunks (which
+ * share the same underlying channel) do not stomp each other's cursor.
+ */
+ private void readAtOffset(ByteBuffer[] buffers, long fileOffset) throws
IOException {
+ long pos = fileOffset;
+ for (ByteBuffer buffer : buffers) {
+ while (buffer.hasRemaining()) {
+ int n = dataIn.read(buffer, pos);
+ if (n <= 0) {
+ if (buffer.hasRemaining()) {
+ throw new IOException("Failed to read chunk data at offset " + pos
+ + " for block chunk " + chunkInfo.getChunkName());
+ }
+ break;
+ }
+ pos += n;
+ }
+ }
+ }
Review Comment:
- We should keep looping for the same buffer until it is filled up.
- We should throw EOFException when a buffer cannot be filled up.
```java
private void readAtOffset(ByteBuffer[] buffers, long fileOffset) throws
IOException {
long pos = fileOffset;
for (ByteBuffer buffer : buffers) {
final int remaining = buffer.remaining();
final long read = readAtOffset(buffer, pos);
if (buffer.hasRemaining()) {
throw new EOFException("Read only " + read + "bytes but expected to
read " + remaining
+ " bytes at offset " + pos + " for chunk " +
chunkInfo.getChunkName());
}
pos += read;
}
}
```
```java
private long readAtOffset(ByteBuffer buffer, long fileOffset) throws
IOException {
long pos = fileOffset;
while (buffer.hasRemaining()) {
final int n = dataIn.read(buffer, pos);
if (n < 0) {
break;
}
pos += n;
}
return pos - fileOffset;
}
```
##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/StreamBlockInputStream.java:
##########
@@ -330,8 +330,9 @@ protected synchronized void acquireClient() throws
IOException {
throw new IOException("Unexpected client class: " +
client.getClass().getName() + ", " + pipeline);
}
- xceiverClient = (XceiverClientGrpc) client;
+ xceiverClient = (XceiverClientGrpc) client;
}
+ return xceiverClient;
Review Comment:
All the changes in this file are not needed.
--
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]