|
49 | 49 | import java.util.concurrent.atomic.AtomicLong;
|
50 | 50 | import java.util.List;
|
51 | 51 | import java.util.concurrent.CompletableFuture;
|
| 52 | +import java.util.function.Consumer; |
52 | 53 | import java.util.function.IntFunction;
|
53 | 54 |
|
54 | 55 | import org.apache.hadoop.classification.InterfaceAudience;
|
55 | 56 | import org.apache.hadoop.classification.InterfaceStability;
|
56 | 57 | import org.apache.hadoop.conf.Configuration;
|
57 | 58 | import org.apache.hadoop.fs.impl.StoreImplementationUtils;
|
| 59 | +import org.apache.hadoop.fs.impl.VectorIOBufferPool; |
58 | 60 | import org.apache.hadoop.fs.permission.FsPermission;
|
59 | 61 | import org.apache.hadoop.fs.statistics.IOStatistics;
|
60 | 62 | import org.apache.hadoop.fs.statistics.IOStatisticsAggregator;
|
61 | 63 | import org.apache.hadoop.fs.statistics.IOStatisticsContext;
|
62 | 64 | import org.apache.hadoop.fs.statistics.IOStatisticsSource;
|
63 | 65 | import org.apache.hadoop.fs.statistics.BufferedIOStatisticsOutputStream;
|
64 | 66 | import org.apache.hadoop.fs.statistics.impl.IOStatisticsStore;
|
| 67 | +import org.apache.hadoop.io.ByteBufferPool; |
65 | 68 | import org.apache.hadoop.io.IOUtils;
|
66 | 69 | import org.apache.hadoop.io.nativeio.NativeIO;
|
67 | 70 | import org.apache.hadoop.util.Progressable;
|
68 | 71 | import org.apache.hadoop.util.Shell;
|
69 | 72 | import org.apache.hadoop.util.StringUtils;
|
70 | 73 |
|
| 74 | +import static org.apache.hadoop.fs.VectoredReadUtils.LOG_BYTE_BUFFER_RELEASED; |
71 | 75 | import static org.apache.hadoop.fs.VectoredReadUtils.sortRangeList;
|
72 | 76 | import static org.apache.hadoop.fs.VectoredReadUtils.validateRangeRequest;
|
73 | 77 | import static org.apache.hadoop.fs.impl.PathCapabilitiesSupport.validatePathCapabilityArgs;
|
@@ -319,74 +323,131 @@ AsynchronousFileChannel getAsyncChannel() throws IOException {
|
319 | 323 | @Override
|
320 | 324 | public void readVectored(List<? extends FileRange> ranges,
|
321 | 325 | IntFunction<ByteBuffer> allocate) throws IOException {
|
| 326 | + readVectored(ranges, allocate, LOG_BYTE_BUFFER_RELEASED); |
| 327 | + } |
| 328 | + |
| 329 | + @Override |
| 330 | + public void readVectored(final List<? extends FileRange> ranges, |
| 331 | + final IntFunction<ByteBuffer> allocate, |
| 332 | + final Consumer<ByteBuffer> release) throws IOException { |
322 | 333 |
|
323 | 334 | // Validate, but do not pass in a file length as it may change.
|
324 | 335 | List<? extends FileRange> sortedRanges = sortRangeList(ranges);
|
325 |
| - // Set up all of the futures, so that we can use them if things fail |
326 |
| - for(FileRange range: sortedRanges) { |
| 336 | + // Set up all of the futures, so that the caller can await on |
| 337 | + // their completion. |
| 338 | + for (FileRange range: sortedRanges) { |
327 | 339 | validateRangeRequest(range);
|
328 | 340 | range.setData(new CompletableFuture<>());
|
329 | 341 | }
|
330 |
| - try { |
331 |
| - AsynchronousFileChannel channel = getAsyncChannel(); |
332 |
| - ByteBuffer[] buffers = new ByteBuffer[sortedRanges.size()]; |
333 |
| - AsyncHandler asyncHandler = new AsyncHandler(channel, sortedRanges, buffers); |
334 |
| - for(int i = 0; i < sortedRanges.size(); ++i) { |
335 |
| - FileRange range = sortedRanges.get(i); |
336 |
| - buffers[i] = allocate.apply(range.getLength()); |
337 |
| - channel.read(buffers[i], range.getOffset(), i, asyncHandler); |
338 |
| - } |
339 |
| - } catch (IOException ioe) { |
340 |
| - LOG.debug("Exception occurred during vectored read ", ioe); |
341 |
| - for(FileRange range: sortedRanges) { |
342 |
| - range.getData().completeExceptionally(ioe); |
343 |
| - } |
344 |
| - } |
| 342 | + final ByteBufferPool pool = new VectorIOBufferPool(allocate, release); |
| 343 | + // Initiate the asynchronous reads. |
| 344 | + new AsyncHandler(getAsyncChannel(), |
| 345 | + sortedRanges, |
| 346 | + pool) |
| 347 | + .initiateRead(); |
345 | 348 | }
|
346 | 349 | }
|
347 | 350 |
|
348 | 351 | /**
|
349 | 352 | * A CompletionHandler that implements readFully and translates back
|
350 | 353 | * into the form of CompletionHandler that our users expect.
|
| 354 | + * <p> |
| 355 | + * All reads are started in {@link #initiateRead()}; |
| 356 | + * the handler then receives callbacks on success |
| 357 | + * {@link #completed(Integer, Integer)}, and on failure |
| 358 | + * by {@link #failed(Throwable, Integer)}. |
| 359 | + * These are mapped to the specific range in the read, and its |
| 360 | + * outcome updated. |
351 | 361 | */
|
352 |
| - static class AsyncHandler implements CompletionHandler<Integer, Integer> { |
| 362 | + private static class AsyncHandler implements CompletionHandler<Integer, Integer> { |
| 363 | + /** File channel to read from. */ |
353 | 364 | private final AsynchronousFileChannel channel;
|
| 365 | + |
| 366 | + /** Ranges to fetch. */ |
354 | 367 | private final List<? extends FileRange> ranges;
|
| 368 | + |
| 369 | + /** |
| 370 | + * Pool providing allocate/release operations. |
| 371 | + */ |
| 372 | + private final ByteBufferPool allocateRelease; |
| 373 | + |
| 374 | + /** Buffers being read. */ |
355 | 375 | private final ByteBuffer[] buffers;
|
356 | 376 |
|
357 |
| - AsyncHandler(AsynchronousFileChannel channel, |
358 |
| - List<? extends FileRange> ranges, |
359 |
| - ByteBuffer[] buffers) { |
| 377 | + /** |
| 378 | + * Instantiate. |
| 379 | + * @param channel open channel. |
| 380 | + * @param ranges ranges to read. |
| 381 | + * @param allocateRelease pool for allocating buffers, and releasing on failure |
| 382 | + */ |
| 383 | + AsyncHandler( |
| 384 | + final AsynchronousFileChannel channel, |
| 385 | + final List<? extends FileRange> ranges, |
| 386 | + final ByteBufferPool allocateRelease) { |
360 | 387 | this.channel = channel;
|
361 | 388 | this.ranges = ranges;
|
362 |
| - this.buffers = buffers; |
| 389 | + this.buffers = new ByteBuffer[ranges.size()]; |
| 390 | + this.allocateRelease = allocateRelease; |
| 391 | + } |
| 392 | + |
| 393 | + /** |
| 394 | + * Initiate the read operation. |
| 395 | + * <p> |
| 396 | + * Allocate all buffers, queue the read into the channel, |
| 397 | + * providing this object as the handler. |
| 398 | + */ |
| 399 | + private void initiateRead() { |
| 400 | + for(int i = 0; i < ranges.size(); ++i) { |
| 401 | + FileRange range = ranges.get(i); |
| 402 | + buffers[i] = allocateRelease.getBuffer(false, range.getLength()); |
| 403 | + channel.read(buffers[i], range.getOffset(), i, this); |
| 404 | + } |
363 | 405 | }
|
364 | 406 |
|
| 407 | + /** |
| 408 | + * Callback for a completed full/partial read. |
| 409 | + * <p> |
| 410 | + * For an EOF the number of bytes may be -1. |
| 411 | + * That is mapped to a {@link #failed(Throwable, Integer)} outcome. |
| 412 | + * @param result The bytes read. |
| 413 | + * @param rangeIndex range index within the range list. |
| 414 | + */ |
365 | 415 | @Override
|
366 |
| - public void completed(Integer result, Integer r) { |
367 |
| - FileRange range = ranges.get(r); |
368 |
| - ByteBuffer buffer = buffers[r]; |
| 416 | + public void completed(Integer result, Integer rangeIndex) { |
| 417 | + FileRange range = ranges.get(rangeIndex); |
| 418 | + ByteBuffer buffer = buffers[rangeIndex]; |
369 | 419 | if (result == -1) {
|
370 |
| - failed(new EOFException("Read past End of File"), r); |
| 420 | + // no data was read back. |
| 421 | + failed(new EOFException("Read past End of File"), rangeIndex); |
371 | 422 | } else {
|
372 | 423 | if (buffer.remaining() > 0) {
|
373 | 424 | // issue a read for the rest of the buffer
|
374 |
| - // QQ: What if this fails? It has the same handler. |
375 |
| - channel.read(buffer, range.getOffset() + buffer.position(), r, this); |
| 425 | + channel.read(buffer, range.getOffset() + buffer.position(), rangeIndex, this); |
376 | 426 | } else {
|
377 |
| - // QQ: Why is this required? I think because we don't want the |
378 |
| - // user to read data beyond limit. |
| 427 | + // Flip the buffer and declare success. |
379 | 428 | buffer.flip();
|
380 | 429 | range.getData().complete(buffer);
|
381 | 430 | }
|
382 | 431 | }
|
383 | 432 | }
|
384 | 433 |
|
| 434 | + /** |
| 435 | + * The read of the range failed. |
| 436 | + * <p> |
| 437 | + * Release the buffer supplied for this range, then |
| 438 | + * report to the future as {{completeExceptionally(exc)}} |
| 439 | + * @param exc exception. |
| 440 | + * @param rangeIndex range index within the range list. |
| 441 | + */ |
385 | 442 | @Override
|
386 |
| - public void failed(Throwable exc, Integer r) { |
387 |
| - LOG.debug("Failed while reading range {} ", r, exc); |
388 |
| - ranges.get(r).getData().completeExceptionally(exc); |
| 443 | + public void failed(Throwable exc, Integer rangeIndex) { |
| 444 | + LOG.debug("Failed while reading range {} ", rangeIndex, exc); |
| 445 | + // release the buffer |
| 446 | + allocateRelease.putBuffer(buffers[rangeIndex]); |
| 447 | + // report the failure. |
| 448 | + ranges.get(rangeIndex).getData().completeExceptionally(exc); |
389 | 449 | }
|
| 450 | + |
390 | 451 | }
|
391 | 452 |
|
392 | 453 | @Override
|
|
0 commit comments