eu.stratosphere.nephele.services.iomanager.IOManager Maven / Gradle / Ivy
/***********************************************************************************************************************
* Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu)
*
* Licensed 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 eu.stratosphere.nephele.services.iomanager;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import eu.stratosphere.core.memory.MemorySegment;
/**
* The facade for the provided I/O manager services.
*
*/
public final class IOManager implements UncaughtExceptionHandler
{
/**
* Logging.
*/
private static final Log LOG = LogFactory.getLog(IOManager.class);
/**
* The default temp paths for anonymous Channels.
*/
private final String[] paths;
/**
* A random number generator for the anonymous ChannelIDs.
*/
private final Random random;
/**
* The writer thread 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;
/**
* The number of the next path to use.
*/
private volatile int nextPath;
/**
* A boolean flag indicating whether the close() has already been invoked.
*/
private volatile boolean isClosed = false;
// -------------------------------------------------------------------------
// Constructors / Destructors
// -------------------------------------------------------------------------
/**
* Constructs a new IOManager, writing channels to the system directory.
*/
public IOManager() {
this(System.getProperty("java.io.tmpdir"));
}
/**
* Constructs a new IOManager.
*
* @param path The base directory path for files underlying channels.
*/
public IOManager(String tempDir) {
this(new String[] {tempDir});
}
/**
* Constructs a new IOManager.
*
* @param path
* the basic directory path for files underlying anonymous
* channels.
*/
public IOManager(String[] paths)
{
this.paths = paths;
this.random = new Random();
this.nextPath = 0;
// start a write worker thread for each directory
this.writers = new WriterThread[paths.length];
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 a reader worker thread for each directory
this.readers = new ReaderThread[paths.length];
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();
}
}
/**
* 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.
*/
public synchronized final void shutdown()
{
if (!this.isClosed) {
this.isClosed = true;
// close writing and reading threads with best effort and log problems
// --------------------------------- writer shutdown ----------------------------------
for (int i = 0; i < this.readers.length; i++) {
try {
this.writers[i].shutdown();
}
catch (Throwable t) {
LOG.error("Error while shutting down IO Manager writer thread.", t);
}
}
// --------------------------------- reader shutdown ----------------------------------
for (int i = 0; i < this.readers.length; i++) {
try {
this.readers[i].shutdown();
}
catch (Throwable t) {
LOG.error("Error while shutting down IO Manager reader thread.", t);
}
}
// ------------------------ wait until shutdown is complete ---------------------------
try {
for (int i = 0; i < this.readers.length; i++) {
this.writers[i].join();
}
for (int i = 0; i < this.readers.length; i++) {
this.readers[i].join();
}
}
catch (InterruptedException iex) {}
}
}
/**
* 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.
*/
public final boolean isProperlyShutDown()
{
boolean readersShutDown = true;
for (int i = 0; i < this.readers.length; i++) {
readersShutDown &= this.readers[i].getState() == Thread.State.TERMINATED;
}
boolean writersShutDown = true;
for (int i = 0; i < this.writers.length; i++) {
readersShutDown &= this.writers[i].getState() == Thread.State.TERMINATED;
}
return this.isClosed && writersShutDown && readersShutDown;
}
/* (non-Javadoc)
* @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable)
*/
@Override
public void uncaughtException(Thread t, Throwable e)
{
LOG.fatal("IO Thread '" + t.getName() + "' terminated due to an exception. Closing I/O Manager.", e);
shutdown();
}
// ------------------------------------------------------------------------
// Channel Instantiations
// ------------------------------------------------------------------------
/**
* Creates a new {@link Channel.ID} in one of the temp directories. Multiple
* invocations of this method spread the channels evenly across the different directories.
*
* @return A channel to a temporary directory.
*/
public Channel.ID createChannel()
{
final int num = getNextPathNum();
return new Channel.ID(this.paths[num], num, this.random);
}
/**
* Creates a new {@link Channel.Enumerator}, spreading the channels in a round-robin fashion
* across the temporary file directories.
*
* @return An enumerator for channels.
*/
public Channel.Enumerator createChannelEnumerator()
{
return new Channel.Enumerator(this.paths, this.random);
}
// ------------------------------------------------------------------------
// Reader / Writer instantiations
// ------------------------------------------------------------------------
/**
* Creates a block channel writer that writes to the given channel. The writer writes asynchronously (write-behind),
* accepting write request, carrying them out at some time and returning the written segment to the given queue
* afterwards.
*
* @param channelID The descriptor for the channel to write to.
* @param returnQueue The queue to put the written buffers into.
* @return A block channel writer that writes to the given channel.
* @throws IOException Thrown, if the channel for the writer could not be opened.
*/
public BlockChannelWriter createBlockChannelWriter(Channel.ID channelID,
LinkedBlockingQueue returnQueue)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelWriter(channelID, this.writers[channelID.getThreadNum()].requestQueue, returnQueue, 1);
}
/**
* Creates a block channel writer that writes to the given channel. The writer writes asynchronously (write-behind),
* accepting write request, carrying them out at some time and returning the written segment to the given queue
* afterwards.
*
* The writer will collect a specified number of write requests and carry them out
* in one, effectively writing one block in the size of multiple memory pages.
* Note that this means that no memory segment will reach the return queue before
* the given number of requests are collected, so the number of buffers used with
* the writer should be greater than the number of requests to combine. Ideally,
* the number of memory segments used is a multiple of the number of requests to
* combine.
*
* @param channelID The descriptor for the channel to write to.
* @param returnQueue The queue to put the written buffers into.
* @param numRequestsToCombine The number of write requests to combine to one I/O request.
* @return A block channel writer that writes to the given channel.
* @throws IOException Thrown, if the channel for the writer could not be opened.
*/
public BlockChannelWriter createBlockChannelWriter(Channel.ID channelID,
LinkedBlockingQueue returnQueue, int numRequestsToCombine)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelWriter(channelID, this.writers[channelID.getThreadNum()].requestQueue, returnQueue, numRequestsToCombine);
}
/**
* Creates a block channel writer that writes to the given channel. The writer writes asynchronously (write-behind),
* accepting write request, carrying them out at some time and returning the written segment its return queue afterwards.
*
* @param channelID The descriptor for the channel to write to.
* @return A block channel writer that writes to the given channel.
* @throws IOException Thrown, if the channel for the writer could not be opened.
*/
public BlockChannelWriter createBlockChannelWriter(Channel.ID channelID)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelWriter(channelID, this.writers[channelID.getThreadNum()].requestQueue, new LinkedBlockingQueue(), 1);
}
/**
* Creates a block channel writer that writes to the given channel. The writer writes asynchronously (write-behind),
* accepting write request, carrying them out at some time and returning the written segment its return queue afterwards.
*
* The writer will collect a specified number of write requests and carry them out
* in one, effectively writing one block in the size of multiple memory pages.
* Note that this means that no memory segment will reach the return queue before
* the given number of requests are collected, so the number of buffers used with
* the writer should be greater than the number of requests to combine. Ideally,
* the number of memory segments used is a multiple of the number of requests to
* combine.
*
* @param channelID The descriptor for the channel to write to.
* @param numRequestsToCombine The number of write requests to combine to one I/O request.
* @return A block channel writer that writes to the given channel.
* @throws IOException Thrown, if the channel for the writer could not be opened.
*/
public BlockChannelWriter createBlockChannelWriter(Channel.ID channelID, int numRequestsToCombine)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelWriter(channelID, this.writers[channelID.getThreadNum()].requestQueue, new LinkedBlockingQueue(), numRequestsToCombine);
}
/**
* 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.
*/
public BlockChannelReader createBlockChannelReader(Channel.ID channelID,
LinkedBlockingQueue returnQueue)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelReader(channelID, this.readers[channelID.getThreadNum()].requestQueue, returnQueue, 1);
}
/**
* 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.
*
* The reader will collect a specified number of read requests and carry them out
* in one, effectively reading one block in the size of multiple memory pages.
* Note that this means that no memory segment will reach the return queue before
* the given number of requests are collected, so the number of buffers used with
* the reader should be greater than the number of requests to combine. Ideally,
* the number of memory segments used is a multiple of the number of requests to
* combine.
*
* @param channelID The descriptor for the channel to write to.
* @param returnQueue The queue to put the full buffers into.
* @param numRequestsToCombine The number of read requests to combine to one I/O request.
* @return A block channel reader that reads from the given channel.
* @throws IOException Thrown, if the channel for the reader could not be opened.
*/
public BlockChannelReader createBlockChannelReader(Channel.ID channelID,
LinkedBlockingQueue returnQueue, int numRequestsToCombine)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelReader(channelID, this.readers[channelID.getThreadNum()].requestQueue, returnQueue, numRequestsToCombine);
}
/**
* 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 reader's return queue.
*
* @param channelID The descriptor for the channel to write to.
* @return A block channel reader that reads from the given channel.
* @throws IOException Thrown, if the channel for the reader could not be opened.
*/
public BlockChannelReader createBlockChannelReader(Channel.ID channelID)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelReader(channelID, this.readers[channelID.getThreadNum()].requestQueue, new LinkedBlockingQueue(), 1);
}
/**
* 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 reader's return queue.
*
* The reader will collect a specified number of read requests and carry them out
* in one, effectively reading one block in the size of multiple memory pages.
* Note that this means that no memory segment will reach the return queue before
* the given number of requests are collected, so the number of buffers used with
* the reader should be greater than the number of requests to combine. Ideally,
* the number of memory segments used is a multiple of the number of requests to
* combine.
*
* @param channelID The descriptor for the channel to write to.
* @param numRequestsToCombine The number of write requests to combine to one I/O request.
* @return A block channel reader that reads from the given channel.
* @throws IOException Thrown, if the channel for the reader could not be opened.
*/
public BlockChannelReader createBlockChannelReader(Channel.ID channelID, int numRequestsToCombine)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BlockChannelReader(channelID, this.readers[channelID.getThreadNum()].requestQueue,
new LinkedBlockingQueue(), numRequestsToCombine);
}
/**
* 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.
*/
public BulkBlockChannelReader createBulkBlockChannelReader(Channel.ID channelID,
List targetSegments, int numBlocks)
throws IOException
{
if (this.isClosed) {
throw new IllegalStateException("I/O-Manger is closed.");
}
return new BulkBlockChannelReader(channelID, this.readers[channelID.getThreadNum()].requestQueue, targetSegments, numBlocks);
}
// ========================================================================
// Utilities
// ========================================================================
private final int getNextPathNum()
{
final int next = this.nextPath;
final int newNext = next + 1;
this.nextPath = newNext >= this.paths.length ? 0 : newNext;
return next;
}
// ========================================================================
// I/O Worker Threads
// ========================================================================
/**
* A worker thread for asynchronous read.
*
*/
private static final class ReaderThread extends Thread
{
protected final RequestQueue requestQueue;
private volatile boolean alive;
// ---------------------------------------------------------------------
// Constructors / Destructors
// ---------------------------------------------------------------------
protected ReaderThread()
{
this.requestQueue = new RequestQueue();
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()
{
if (this.alive) {
// shut down the thread
try {
this.alive = false;
this.requestQueue.close();
this.interrupt();
}
catch (Throwable t) {}
}
// notify all pending write requests that the thread has been shut down
IOException ioex = new IOException("IO-Manager has been closed.");
while (!this.requestQueue.isEmpty()) {
ReadRequest request = this.requestQueue.poll();
request.requestDone(ioex);
}
}
// ---------------------------------------------------------------------
// Main loop
// ---------------------------------------------------------------------
@Override
public void run()
{
while (this.alive)
{
// get the next buffer. ignore interrupts that are not due to a shutdown.
ReadRequest request = null;
while (request == null) {
try {
request = this.requestQueue.take();
}
catch (InterruptedException iex) {
if (!this.alive) {
// exit
return;
}
}
}
// remember any IO exception that occurs, so it can be reported to the writer
IOException ioex = null;
try {
// read buffer from the specified channel
request.read();
}
catch (IOException e) {
ioex = e;
}
catch (Throwable t) {
ioex = new IOException("The buffer could not be read: " + t.getMessage(), t);
IOManager.LOG.error("I/O reading thread encountered an error" +
t.getMessage() == null ? "." : ": ", t);
}
// invoke the processed buffer handler of the request issuing reader object
request.requestDone(ioex);
} // 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 RequestQueue requestQueue;
private volatile boolean alive;
// ---------------------------------------------------------------------
// Constructors / Destructors
// ---------------------------------------------------------------------
protected WriterThread()
{
this.requestQueue = new RequestQueue();
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()
{
if (this.alive) {
// shut down the thread
try {
this.alive = false;
this.requestQueue.close();
this.interrupt();
}
catch (Throwable t) {}
// notify all pending write requests that the thread has been shut down
IOException ioex = new IOException("Writer thread has been closed.");
while (!this.requestQueue.isEmpty())
{
WriteRequest request = this.requestQueue.poll();
request.requestDone(ioex);
}
}
}
// ---------------------------------------------------------------------
// Main loop
// ---------------------------------------------------------------------
@Override
public void run()
{
while (this.alive) {
WriteRequest request = null;
// get the next buffer. ignore interrupts that are not due to a shutdown.
while (request == null) {
try {
request = requestQueue.take();
}
catch (InterruptedException iex) {
if (!this.alive) {
// exit
return;
}
}
}
// remember any IO exception that occurs, so it can be reported to the writer
IOException ioex = null;
try {
// write buffer to the specified channel
request.write();
}
catch (IOException e) {
ioex = e;
}
catch (Throwable t) {
ioex = new IOException("The buffer could not be written: " + t.getMessage(), t);
IOManager.LOG.error("I/O reading thread encountered an error" +
t.getMessage() == null ? "." : ": ", t);
}
// invoke the processed buffer handler of the request issuing writer object
request.requestDone(ioex);
} // end while alive
}
}; // end writer thread
}