iemejia commented on code in PR #8511:
URL: https://github.com/apache/hadoop/pull/8511#discussion_r4470497304
##########
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/ChecksumFileSystem.java:
##########
@@ -493,22 +493,67 @@ public void readVectored(final List<? extends FileRange>
ranges,
}
List<CombinedFileRange> checksumRanges = findChecksumRanges(dataRanges,
bytesPerSum, minSeek, maxSize);
- sums.readVectored(checksumRanges, allocate, release);
- datas.readVectored(dataRanges, allocate, release);
+ try {
+ sums.readVectored(checksumRanges, allocate, release);
+ datas.readVectored(dataRanges, allocate, release);
+ } catch (IOException | RuntimeException e) {
+ // No buffers have been exposed to the caller yet. Reads already
started
+ // may still be in flight, so release their buffers only after success.
+ releaseReadBuffers(checksumRanges, release);
+ releaseReadBuffers(dataRanges, release);
+ throw e;
+ }
for(CombinedFileRange checksumRange: checksumRanges) {
+ List<CompletableFuture<ByteBuffer>> verifications = new ArrayList<>();
for(FileRange dataRange: checksumRange.getUnderlying()) {
// when we have both the ranges, validate the checksum
CompletableFuture<ByteBuffer> result =
checksumRange.getData().thenCombineAsync(dataRange.getData(),
(sumBuffer, dataBuffer) ->
checkBytes(sumBuffer, checksumRange.getOffset(),
- dataBuffer, dataRange.getOffset(), bytesPerSum,
file));
+ dataBuffer, dataRange.getOffset(), bytesPerSum,
file))
+ .whenComplete((buffer, ex) -> {
+ // Failed reads release their own buffers. A successful
data
+ // read whose verification failed is never returned to the
caller.
+ if (ex != null &&
!dataRange.getData().isCompletedExceptionally()) {
+ release.accept(dataRange.getData().join());
+ }
+ });
+ verifications.add(result);
// Now, slice the read data range to the user's ranges
for(FileRange original: ((CombinedFileRange)
dataRange).getUnderlying()) {
original.setData(result.thenApply(
(b) -> VectoredReadUtils.sliceTo(b, dataRange.getOffset(),
original)));
Review Comment:
Thanks for the follow-up review. Addressed in
a419df0d808e80fe6d146089dada638fa0c8f057 using the copy-after-verification
approach.
`ChecksumFileSystem` now allocates each caller-range buffer synchronously in
`readVectored()`, copies the verified bytes from the internal data buffer, and
returns the exact allocated object rather than a slice. Internal data buffers
are released after all dependent copies finish, including on failure. Both the
stream and filesystem now report `false` for `VECTOREDIO_BUFFERS_SLICED`, and
the “Buffer Slicing” section and the proposed release-contract wording have
been updated to remove the slice clause.
The two-argument overload retains its no-op release behavior. The docs now
explain that explicit recycling of internal buffers requires the three-argument
overload, and that callers migrating from a capturing-allocator workaround must
not separately release buffers already released through the callback. Only
successfully returned range buffers remain caller-owned.
Regression coverage checks exact buffer identity, synchronous allocation,
unaligned ranges, oversized pooled buffers, copy failures, and preservation of
successful sibling results with both heap and direct buffers.
All **215 targeted test cases pass with JDK 17** across
`TestLocalFSContractVectoredRead`, `TestRawLocalContractVectoredRead`,
`TestVectoredReadUtils`, `TestChecksumFileSystem`, and
`TestRawLocalFileSystemVectoredRead`. Checkstyle reports no violations on
changed lines, and `git diff --check` passes.
##########
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/ChecksumFileSystem.java:
##########
@@ -493,22 +493,67 @@ public void readVectored(final List<? extends FileRange>
ranges,
}
List<CombinedFileRange> checksumRanges = findChecksumRanges(dataRanges,
bytesPerSum, minSeek, maxSize);
- sums.readVectored(checksumRanges, allocate, release);
- datas.readVectored(dataRanges, allocate, release);
+ try {
+ sums.readVectored(checksumRanges, allocate, release);
+ datas.readVectored(dataRanges, allocate, release);
+ } catch (IOException | RuntimeException e) {
Review Comment:
Addressed in a419df0d808e80fe6d146089dada638fa0c8f057. The checksum/data
setup handler now also catches `Error` and arranges cleanup for internal reads
already submitted. Their buffers are released only after successful read
completion; unsubmitted caller buffers are released immediately, and the
original setup failure is preserved.
Tests cover injected errors during caller, checksum, and data allocation,
plus partial checksum/data submission with both pending and immediately
completed reads. Caller-range futures are also completed exceptionally on setup
failure.
##########
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/ChecksumFileSystem.java:
##########
@@ -493,22 +493,67 @@ public void readVectored(final List<? extends FileRange>
ranges,
}
List<CombinedFileRange> checksumRanges = findChecksumRanges(dataRanges,
bytesPerSum, minSeek, maxSize);
- sums.readVectored(checksumRanges, allocate, release);
- datas.readVectored(dataRanges, allocate, release);
+ try {
+ sums.readVectored(checksumRanges, allocate, release);
+ datas.readVectored(dataRanges, allocate, release);
+ } catch (IOException | RuntimeException e) {
+ // No buffers have been exposed to the caller yet. Reads already
started
+ // may still be in flight, so release their buffers only after success.
+ releaseReadBuffers(checksumRanges, release);
+ releaseReadBuffers(dataRanges, release);
+ throw e;
+ }
for(CombinedFileRange checksumRange: checksumRanges) {
+ List<CompletableFuture<ByteBuffer>> verifications = new ArrayList<>();
for(FileRange dataRange: checksumRange.getUnderlying()) {
// when we have both the ranges, validate the checksum
CompletableFuture<ByteBuffer> result =
checksumRange.getData().thenCombineAsync(dataRange.getData(),
(sumBuffer, dataBuffer) ->
checkBytes(sumBuffer, checksumRange.getOffset(),
- dataBuffer, dataRange.getOffset(), bytesPerSum,
file));
+ dataBuffer, dataRange.getOffset(), bytesPerSum,
file))
+ .whenComplete((buffer, ex) -> {
+ // Failed reads release their own buffers. A successful
data
+ // read whose verification failed is never returned to the
caller.
+ if (ex != null &&
!dataRange.getData().isCompletedExceptionally()) {
+ release.accept(dataRange.getData().join());
+ }
+ });
+ verifications.add(result);
// Now, slice the read data range to the user's ranges
for(FileRange original: ((CombinedFileRange)
dataRange).getUnderlying()) {
original.setData(result.thenApply(
(b) -> VectoredReadUtils.sliceTo(b, dataRange.getOffset(),
original)));
}
}
+ // Release the checksum buffer once all verifications using it are
complete.
+ // Without this, buffers allocated through the caller's allocator for
checksum
+ // data are never released, since the caller has no reference to them.
+ CompletableFuture.allOf(verifications.toArray(new
CompletableFuture[0]))
+ .whenComplete((unused, ex) -> {
+ // A failed checksum read already releases its buffer in the
inner stream.
+ // Otherwise release it even if verification or a data read
failed.
+ if (!checksumRange.getData().isCompletedExceptionally()) {
+ release.accept(checksumRange.getData().join());
Review Comment:
Addressed in a419df0d808e80fe6d146089dada638fa0c8f057. Cleanup that has no
caller-visible future now goes through a helper which catches release-callback
failures and calls `LOG.warn`, rather than losing them in discarded completion
stages or masking the original synchronous setup failure.
This covers checksum cleanup, the new internal data-buffer cleanup after
copying, and cleanup of successful internal reads after synchronous setup
fails. Regression tests verify logging for checksum/data cleanup and
setup-failure cleanup, while retaining exact release-count and buffer-identity
assertions.
##########
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/PositionedReadable.java:
##########
@@ -141,16 +141,20 @@ default void readVectored(List<? extends FileRange>
ranges,
/**
* Extension of {@link #readVectored(List, IntFunction)} where a {@code
release(buffer)}
- * operation may be invoked if problems surface during reads.
+ * operation may be invoked if problems surface during reads, or to release
+ * internal buffers which are not returned to the caller.
* <p>
- * The {@code release} operation is invoked after an IOException
- * to return the actively buffer to a pool before reporting a failure
- * in the future.
+ * For buffers holding caller-visible range data, the {@code release}
operation
+ * is invoked after an IOException to return the active buffer to a pool
before
+ * reporting a failure in the future. Buffers used only internally, such as
+ * checksum buffers, should be released once they are no longer needed,
whether
+ * the read succeeds or fails. For each allocation, a buffer must not be
released
+ * more than once, or if it or a slice of it has been returned to the caller.
Review Comment:
Addressed in a419df0d808e80fe6d146089dada638fa0c8f057. Both
`PositionedReadable` and `fsdatainputstream.md` now explicitly state that
`release` may be invoked asynchronously, including after `readVectored()` has
thrown, and that callers must keep the release target valid until all submitted
reads complete.
##########
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/RawLocalFileSystem.java:
##########
@@ -414,10 +424,22 @@ private static class AsyncHandler implements
CompletionHandler<Integer, Integer>
private void initiateRead() {
for(int i = 0; i < ranges.size(); ++i) {
FileRange range = ranges.get(i);
- buffers[i] = allocateRelease.getBuffer(false, range.getLength());
- final ByteBuffer buffer = buffers[i];
- LOG.debug("Reading file range {} into buffer {}", range,
System.identityHashCode(buffer));
- channel.read(buffer, range.getOffset(), i, this);
+ try {
+ buffers[i] = allocateRelease.getBuffer(false, range.getLength());
+ final ByteBuffer buffer = buffers[i];
+ // Pooled buffers may be larger than the requested range.
+ buffer.limit(range.getLength());
+ LOG.debug("Reading file range {} into buffer {}", range,
System.identityHashCode(buffer));
+ channel.read(buffer, range.getOffset(), i, this);
+ } catch (RuntimeException e) {
Review Comment:
Addressed in a419df0d808e80fe6d146089dada638fa0c8f057. Allocation and
initial submission now catch `RuntimeException | Error`, release the affected
allocated buffer, settle unsubmitted range futures, and rethrow the original
failure. Earlier submitted reads retain ownership until their callbacks finish.
Channel-open failures and partial-read resubmissions also handle `Error`.
Deterministic tests inject `OutOfMemoryError("Direct buffer memory")` without
exhausting memory and cover both pending and immediately completed earlier
reads, as well as throwing release callbacks.
##########
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/RawLocalFileSystem.java:
##########
@@ -349,8 +349,18 @@ public void readVectored(final List<? extends FileRange>
ranges,
range.setData(new CompletableFuture<>());
}
final ByteBufferPool pool = new VectorIOBufferPool(allocate, release);
+ final AsynchronousFileChannel channel;
+ try {
+ channel = getAsyncChannel();
Review Comment:
Agreed. I have left the pre-existing async-channel initialization issue
unchanged in this PR so it can be handled in a separate JIRA, as suggested.
--
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]