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

org.apache.flink.runtime.io.disk.iomanager.IOManagerAsync Maven / Gradle / Ivy

There is a newer version: 1.5.1
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.flink.runtime.io.disk.iomanager;

import org.apache.flink.core.memory.MemorySegment;
import org.apache.flink.runtime.io.network.buffer.Buffer;
import org.apache.flink.runtime.util.EnvironmentInformation;
import org.apache.flink.util.ShutdownHookUtil;

import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.apache.flink.util.Preconditions.checkState;

/**
 * A version of the {@link IOManager} that uses asynchronous I/O.
 */
public class IOManagerAsync extends IOManager implements UncaughtExceptionHandler {

	/** The writer threads used for asynchronous block oriented channel writing. */
	private final WriterThread[] writers;

	/** The reader threads used for asynchronous block oriented channel reading. */
	private final ReaderThread[] readers;

	/** Flag to signify that the IOManager has been shut down already */
	private final AtomicBoolean isShutdown = new AtomicBoolean();

	/** Shutdown hook to make sure that the directories are removed on exit */
	private final Thread shutdownHook;

	private final int bufferedReadSize;
	private final int bufferedWriteSize;

	// -------------------------------------------------------------------------
	//               Constructors / Destructors
	// -------------------------------------------------------------------------

	/**
	 * Constructs a new asynchronous I/O manger, writing files to the system 's temp directory.
	 */
	public IOManagerAsync() {
		this(1);
	}

	public IOManagerAsync(int numThreads) {
		this(-1, -1, numThreads);
	}

	/**
	 * Constructs a new asynchronous I/O manger, writing file to the given directory.
	 *
	 * @param tempDir The directory to write temporary files to.
	 */
	public IOManagerAsync(String[] tempDir) {
		this(tempDir, -1, -1, 1);
	}

	public IOManagerAsync(int bufferedReadSize, int bufferedWriteSize) {
		this(bufferedReadSize, bufferedWriteSize, 1);
	}

	public IOManagerAsync(int bufferedReadSize, int bufferedWriteSize, int numThreads) {
		this(new String[] {EnvironmentInformation.getTemporaryFileDirectory()}, bufferedReadSize, bufferedWriteSize, numThreads);
	}

	/**
	 * Constructs a new asynchronous I/O manger, writing file round robin across the given directories.
	 *
	 * @param tempDirs The directories to write temporary files to.
	 */
	public IOManagerAsync(String[] tempDirs, int bufferedReadSize, int bufferedWriteSize, int numThreads) {
		super(tempDirs, numThreads);

		// start all worker threads for writer
		this.writers = new WriterThread[numThreads];
		this.bufferedReadSize = bufferedReadSize;
		this.bufferedWriteSize = bufferedWriteSize;
		for (int i = 0; i < this.writers.length; i++) {
			final WriterThread t = new WriterThread();
			this.writers[i] = t;
			t.setName("IOManager writer thread #" + (i + 1));
			t.setDaemon(true);
			t.setUncaughtExceptionHandler(this);
			t.start();
		}

		// start all worker threads for reader
		this.readers = new ReaderThread[numThreads];
		for (int i = 0; i < this.readers.length; i++) {
			final ReaderThread t = new ReaderThread();
			this.readers[i] = t;
			t.setName("IOManager reader thread #" + (i + 1));
			t.setDaemon(true);
			t.setUncaughtExceptionHandler(this);
			t.start();
		}

		// install a shutdown hook that makes sure the temp directories get deleted
		this.shutdownHook = ShutdownHookUtil.addShutdownHook(this::shutdown, getClass().getSimpleName(), LOG);
	}

	/**
	 * Close method. Shuts down the reader and writer threads immediately, not waiting for their
	 * pending requests to be served. This method waits until the threads have actually ceased their
	 * operation.
	 */
	@Override
	public void shutdown() {
		// mark shut down and exit if it already was shut down
		if (!isShutdown.compareAndSet(false, true)) {
			return;
		}

		// Remove shutdown hook to prevent resource leaks
		ShutdownHookUtil.removeShutdownHook(shutdownHook, getClass().getSimpleName(), LOG);

		try {
			if (LOG.isDebugEnabled()) {
				LOG.debug("Shutting down I/O manager.");
			}

			// close writing and reading threads with best effort and log problems
			// first notify all to close, then wait until all are closed

			for (WriterThread wt : writers) {
				try {
					wt.shutdown();
				}
				catch (Throwable t) {
					LOG.error("Error while shutting down IO Manager writer thread.", t);
				}
			}
			for (ReaderThread rt : readers) {
				try {
					rt.shutdown();
				}
				catch (Throwable t) {
					LOG.error("Error while shutting down IO Manager reader thread.", t);
				}
			}
			try {
				for (WriterThread wt : writers) {
					wt.join();
				}
				for (ReaderThread rt : readers) {
					rt.join();
				}
			}
			catch (InterruptedException iex) {
				// ignore this on shutdown
			}
		}
		finally {
			// make sure we call the super implementation in any case and at the last point,
			// because this will clean up the I/O directories
			super.shutdown();
		}
	}

	/**
	 * Utility method to check whether the IO manager has been properly shut down. The IO manager is considered
	 * to be properly shut down when it is closed and its threads have ceased operation.
	 *
	 * @return True, if the IO manager has properly shut down, false otherwise.
	 */
	@Override
	public boolean isProperlyShutDown() {
		boolean readersShutDown = true;
		for (ReaderThread rt : readers) {
			readersShutDown &= rt.getState() == Thread.State.TERMINATED;
		}

		boolean writersShutDown = true;
		for (WriterThread wt : writers) {
			writersShutDown &= wt.getState() == Thread.State.TERMINATED;
		}

		return isShutdown.get() && readersShutDown && writersShutDown && super.isProperlyShutDown();
	}


	@Override
	public void uncaughtException(Thread t, Throwable e) {
		LOG.error("IO Thread '" + t.getName() + "' terminated due to an exception. Shutting down I/O Manager.", e);
		shutdown();
	}

	// ------------------------------------------------------------------------
	//                        Reader / Writer instantiations
	// ------------------------------------------------------------------------

	@Override
	public BlockChannelWriter createBlockChannelWriter(FileIOChannel.ID channelID,
								LinkedBlockingQueue returnQueue) throws IOException
	{
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");
		return new AsynchronousBlockWriter(channelID, this.writers[channelID.getThreadNum()].requestScheduler, returnQueue, bufferedWriteSize);
	}

	@Override
	public BlockChannelWriterWithCallback createBlockChannelWriter(FileIOChannel.ID channelID, RequestDoneCallback callback) throws IOException {
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");
		return new AsynchronousBlockWriterWithCallback(channelID, this.writers[channelID.getThreadNum()].requestScheduler, callback, bufferedWriteSize);
	}

	/**
	 * Creates a block channel reader that reads blocks from the given channel. The reader reads asynchronously,
	 * such that a read request is accepted, carried out at some (close) point in time, and the full segment
	 * is pushed to the given queue.
	 *
	 * @param channelID The descriptor for the channel to write to.
	 * @param returnQueue The queue to put the full buffers into.
	 * @return A block channel reader that reads from the given channel.
	 * @throws IOException Thrown, if the channel for the reader could not be opened.
	 */
	@Override
	public BlockChannelReader createBlockChannelReader(FileIOChannel.ID channelID,
										LinkedBlockingQueue returnQueue) throws IOException
	{
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");
		return new AsynchronousBlockReader(channelID, this.readers[channelID.getThreadNum()].requestScheduler, returnQueue, bufferedReadSize);
	}

	@Override
	public BufferFileWriter createBufferFileWriter(FileIOChannel.ID channelID) throws IOException {
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");

		return new AsynchronousBufferFileWriter(channelID, writers[channelID.getThreadNum()].requestScheduler, bufferedWriteSize);
	}

	@Override
	public BufferFileReader createBufferFileReader(FileIOChannel.ID channelID, RequestDoneCallback callback) throws IOException {
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");

		return new AsynchronousBufferFileReader(channelID, readers[channelID.getThreadNum()].requestScheduler, callback, bufferedReadSize);
	}

	@Override
	public BufferFileSegmentReader createBufferFileSegmentReader(FileIOChannel.ID channelID, RequestDoneCallback callback) throws IOException {
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");

		return new AsynchronousBufferFileSegmentReader(channelID, readers[channelID.getThreadNum()].requestScheduler, callback);
	}

	@Override
	public BufferFileWriter createStreamFileWriter(FileIOChannel.ID channelID) throws IOException {
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");

		return new AsynchronousBufferFileWriter(channelID, writers[channelID.getThreadNum()].requestScheduler, bufferedWriteSize, false);
	}

	@Override
	public BufferFileReader createStreamFileReader(FileIOChannel.ID channelID, RequestDoneCallback callback) throws IOException {
		checkState(!isShutdown.get(), "I/O-Manger is shut down.");

		return new AsynchronousBufferFileReader(channelID, readers[channelID.getThreadNum()].requestScheduler, callback, bufferedReadSize, false);
	}

	/**
	 * Creates a block channel reader that reads all blocks from the given channel directly in one bulk.
	 * The reader draws segments to read the blocks into from a supplied list, which must contain as many
	 * segments as the channel has blocks. After the reader is done, the list with the full segments can be
	 * obtained from the reader.
	 * 

* If a channel is not to be read in one bulk, but in multiple smaller batches, a * {@link BlockChannelReader} should be used. * * @param channelID The descriptor for the channel to write to. * @param targetSegments The list to take the segments from into which to read the data. * @param numBlocks The number of blocks in the channel to read. * @return A block channel reader that reads from the given channel. * @throws IOException Thrown, if the channel for the reader could not be opened. */ @Override public BulkBlockChannelReader createBulkBlockChannelReader(FileIOChannel.ID channelID, List targetSegments, int numBlocks) throws IOException { checkState(!isShutdown.get(), "I/O-Manger is shut down."); return new AsynchronousBulkBlockReader(channelID, this.readers[channelID.getThreadNum()].requestScheduler, targetSegments, numBlocks, bufferedReadSize); } // ------------------------------------------------------------------------- // For Testing // ------------------------------------------------------------------------- IORequestScheduler getReadRequestScheduler(FileIOChannel.ID channelID) { return this.readers[channelID.getThreadNum()].requestScheduler; } IORequestScheduler getWriteRequestScheduler(FileIOChannel.ID channelID) { return this.writers[channelID.getThreadNum()].requestScheduler; } int getNumReaders() { return readers.length; } int getNumWriters() { return writers.length; } // ------------------------------------------------------------------------- // I/O Worker Threads // ------------------------------------------------------------------------- /** * A worker thread for asynchronous reads. */ private static final class ReaderThread extends Thread { protected final IORequestScheduler requestScheduler; private volatile boolean alive; // --------------------------------------------------------------------- // Constructors / Destructors // --------------------------------------------------------------------- protected ReaderThread() { this.requestScheduler = new IORequestScheduler<>(); this.alive = true; } /** * Shuts the thread down. This operation does not wait for all pending requests to be served, halts the thread * immediately. All buffers of pending requests are handed back to their channel readers and an exception is * reported to them, declaring their request queue as closed. */ protected void shutdown() { synchronized (this) { if (alive) { alive = false; requestScheduler.close(); interrupt(); } try { join(1000); } catch (InterruptedException ignored) {} // notify all pending write requests that the thread has been shut down IOException ioex = new IOException("IO-Manager has been closed."); requestScheduler.shutDown(ioex); } } // --------------------------------------------------------------------- // Main loop // --------------------------------------------------------------------- @Override public void run() { while (alive) { // get the next buffer. ignore interrupts that are not due to a shutdown. RequestQueue requestQueue = null; while (alive && requestQueue == null) { try { requestQueue = requestScheduler.nextRequestQueue(); } catch (InterruptedException e) { if (!this.alive) { return; } else { IOManagerAsync.LOG.warn(Thread.currentThread() + " was interrupted without shutdown."); } } } while (!requestQueue.isEmpty()) { ReadRequest readRequest = requestQueue.poll(); // remember any IO exception that occurs, so it can be reported to the writer IOException ioex = null; try { // read buffer from the specified channel readRequest.read(); } catch (IOException e) { ioex = e; } catch (Throwable t) { ioex = new IOException("The buffer could not be read: " + t.getMessage(), t); IOManagerAsync.LOG.error("I/O reading thread encountered an error" + (t.getMessage() == null ? "." : ": " + t.getMessage()), t); } // invoke the processed buffer handler of the request issuing reader object try { readRequest.requestDone(ioex); } catch (Throwable t) { IOManagerAsync.LOG.error("The handler of the request-complete-callback threw an exception" + (t.getMessage() == null ? "." : ": " + t.getMessage()), t); } } requestScheduler.requestQueueProcessed(requestQueue); } // end while alive } } // end reading thread /** * A worker thread that asynchronously writes the buffers to disk. */ private static final class WriterThread extends Thread { protected final IORequestScheduler requestScheduler; private volatile boolean alive; // --------------------------------------------------------------------- // Constructors / Destructors // --------------------------------------------------------------------- protected WriterThread() { this.requestScheduler = new IORequestScheduler<>(); this.alive = true; } /** * Shuts the thread down. This operation does not wait for all pending requests to be served, halts the thread * immediately. All buffers of pending requests are handed back to their channel writers and an exception is * reported to them, declaring their request queue as closed. */ protected void shutdown() { synchronized (this) { if (alive) { alive = false; requestScheduler.close(); interrupt(); } try { join(1000); } catch (InterruptedException ignored) {} // notify all pending write requests that the thread has been shut down IOException ioex = new IOException("IO-Manager has been closed."); requestScheduler.shutDown(ioex); } } // --------------------------------------------------------------------- // Main loop // --------------------------------------------------------------------- @Override public void run() { while (this.alive) { RequestQueue requestQueue = null; // get the next buffer. ignore interrupts that are not due to a shutdown. while (alive && requestQueue == null) { try { requestQueue = requestScheduler.nextRequestQueue(); } catch (InterruptedException e) { if (!this.alive) { return; } else { IOManagerAsync.LOG.warn(Thread.currentThread() + " was interrupted without shutdown."); } } } while (!requestQueue.isEmpty()) { WriteRequest writeRequest = requestQueue.poll(); // remember any IO exception that occurs, so it can be reported to the writer IOException ioex = null; try { // write buffer to the specified channel writeRequest.write(); } catch (IOException e) { ioex = e; } catch (Throwable t) { ioex = new IOException("The buffer could not be written: " + t.getMessage(), t); IOManagerAsync.LOG.error("I/O writing thread encountered an error" + (t.getMessage() == null ? "." : ": " + t.getMessage()), t); } // invoke the processed buffer handler of the request issuing writer object try { writeRequest.requestDone(ioex); } catch (Throwable t) { IOManagerAsync.LOG.error("The handler of the request-complete-callback threw an exception" + (t.getMessage() == null ? "." : ": " + t.getMessage()), t); } } requestScheduler.requestQueueProcessed(requestQueue); } // end while alive } }; // end writer thread }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy