-
Notifications
You must be signed in to change notification settings - Fork 634
HDDS-15424. Fix Concurrent positional read #11102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -429,6 +429,109 @@ 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 | ||
| * {@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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we synchronize local positioned reads on the shared block FileChannel, or use positional FileChannel reads? Each LocalChunkInputStream locks its own instance, while all chunks in the block receive the same blockFileInputStream, so preads to different chunks can still interleave position(...).read(...) and use the wrong offset.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup, you're right about the positional FileChannel, I made the change and please review again. |
||
| 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; | ||
| } | ||
|
|
||
| acquireClient(); | ||
|
|
||
| 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(); | ||
|
|
||
| final ByteBuffer[] readBuffers = readChunk(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. | ||
| */ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.