All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.druid.frame.channel.ReadableInputStreamFrameChannel Maven / Gradle / Ivy

There is a newer version: 31.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.druid.frame.channel;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import org.apache.commons.io.IOUtils;
import org.apache.druid.frame.Frame;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.concurrent.Execs;

import java.io.InputStream;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Channel backed by an {@link InputStream}.
 * 

* Frame channels are expected to be nonblocking, but InputStreams cannot be read in nonblocking fashion. * This implementation deals with that by using an {@link ExecutorService} to read from the stream in a * separate thread. */ public class ReadableInputStreamFrameChannel implements ReadableFrameChannel { private final InputStream inputStream; private final ReadableByteChunksFrameChannel delegate; private final Object lock = new Object(); @GuardedBy("lock") private final byte[] buffer = new byte[8 * 1024]; @GuardedBy("lock") private long totalInputStreamBytesRead = 0; @GuardedBy("lock") private boolean inputStreamFinished = false; @GuardedBy("lock") private boolean inputStreamError = false; private boolean isStarted = false; private volatile boolean keepReading = true; private final Object readMonitor = new Object(); private final ExecutorService executorService; /** * Parameter for manipulating retry sleep duration */ private static final int BASE_SLEEP_MILLIS = 100; /** * Parameter for manipulating retry sleep duration. */ private static final int MAX_SLEEP_MILLIS = 2000; /** * Private because outside callers use {@link #open}. */ private ReadableInputStreamFrameChannel( final InputStream inputStream, final ReadableByteChunksFrameChannel delegate, final ExecutorService executorService ) { this.inputStream = inputStream; this.delegate = delegate; this.executorService = executorService; } /** * Create an instance of this class and immediately start reading from the provided InputStream. */ public static ReadableInputStreamFrameChannel open( InputStream inputStream, String id, ExecutorService executorService, boolean framesOnly ) { return new ReadableInputStreamFrameChannel( inputStream, ReadableByteChunksFrameChannel.create(id, framesOnly), executorService ); } @Override public boolean isFinished() { synchronized (lock) { startReading(); return delegate.isFinished(); } } @Override public boolean canRead() { synchronized (lock) { startReading(); return delegate.canRead(); } } @Override public Frame read() { synchronized (lock) { startReading(); return delegate.read(); } } @Override public ListenableFuture readabilityFuture() { synchronized (lock) { startReading(); return delegate.readabilityFuture(); } } @Override public void close() { synchronized (lock) { inputStreamFinished = true; delegate.close(); IOUtils.closeQuietly(inputStream); } } private void startReading() { // the task to the executor service is submitted only once. if (isStarted) { return; } isStarted = true; executorService.submit(() -> { int nTry = 1; while (true) { if (!keepReading) { try { synchronized (readMonitor) { if (!keepReading) { readMonitor.wait(nextRetrySleepMillis(nTry)); } } synchronized (lock) { if (inputStreamFinished || inputStreamError || delegate.isErrorOrFinished()) { return; } } ++nTry; } catch (InterruptedException e) { // close input stream anyway if the thread interrupts IOUtils.closeQuietly(inputStream); throw new ISE(e, Thread.currentThread().getName() + "interrupted"); } } else { synchronized (lock) { nTry = 1; // Reset the value of try because we are not waiting on the data from the inputStream // if done reading method is called we should not read input stream further if (inputStreamFinished) { delegate.doneWriting(); break; } try { int bytesRead = inputStream.read(buffer); if (bytesRead == -1) { inputStreamFinished = true; delegate.doneWriting(); // eagerly release input stream resources since everything is read. IOUtils.closeQuietly(inputStream); break; } else { ListenableFuture backpressureFuture = delegate.addChunk(Arrays.copyOfRange(buffer, 0, bytesRead)); totalInputStreamBytesRead += bytesRead; if (backpressureFuture != null) { keepReading = false; backpressureFuture.addListener( () -> { synchronized (readMonitor) { keepReading = true; readMonitor.notify(); } }, Execs.directExecutor() ); } else { keepReading = true; // continue adding data to delegate // give up lock so that other threads have a change to do some work } } } catch (Exception e) { //handle exception long currentStreamOffset = totalInputStreamBytesRead; delegate.setError(new ISE(e, "Found error while reading input stream at %d", currentStreamOffset )); inputStreamError = true; // close the stream in case done reading is not called. IOUtils.closeQuietly(inputStream); break; } } } } }); } /** * Function to implement exponential backoff. The calculations are similar to the function that is being used in * {@link org.apache.druid.java.util.common.RetryUtils} but with a different MAX_SLEEP_MILLIS and BASE_SLEEP_MILLIS */ private static long nextRetrySleepMillis(final int nTry) { final double fuzzyMultiplier = Math.min(Math.max(1 + 0.2 * ThreadLocalRandom.current().nextGaussian(), 0), 2); return (long) (Math.min(MAX_SLEEP_MILLIS, BASE_SLEEP_MILLIS * Math.pow(2, nTry - 1)) * fuzzyMultiplier); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy