/* * org.openmicroscopy.shoola.util.concur.AsyncByteBuffer * *------------------------------------------------------------------------------ * Copyright (C) 2006 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.util.concur; //Java imports //Third-party libraries //Application-internal dependencies import org.openmicroscopy.shoola.util.concur.tasks.AsyncProcessor; import org.openmicroscopy.shoola.util.concur.tasks.CmdProcessor; import org.openmicroscopy.shoola.util.concur.tasks.ExecException; import org.openmicroscopy.shoola.util.concur.tasks.Future; import org.openmicroscopy.shoola.util.mem.ReadOnlyByteArray; /** * A specialized byte buffer with an embedded asynchronous producer. * <p>The producer, which has to implement {@link ByteBufferFiller}, is run in * a separate thread so that the buffer can be filled up asynchronously with * respect to the threads that attempt to read from the buffer. Ideally, the * producer object would be passed to the buffer and then de-referenced right * afterward — this hand-off protocol ensures that, at any given time, the * buffer's internal thread is the only one to access that object. If this is * not possible or desirable, then care must be taken in order to protect from * concurrent access to the producer's state — which is possible if the * object is referenced within other threads as well.</p> * <p>This buffer will take care of concurrent read access to the underlying * data. Threads that have a reference to this buffer can safely call its * public methods at any given time. Note that threads that read from the * buffer can't be considered consumers as a read operation leaves the * underlying data untouched. However, it's possible to refill the buffer * with new data by specifying a new producer to the * {@link #setProducer(ByteBufferFiller) setProducer} method. Because the * read methods return objects that access the internal buffer directly (data * is not copied), after a call to the * {@link #setProducer(ByteBufferFiller) setProducer} method, any * {@link ReadOnlyByteArray} object previously obtained from one of the read * methods should be considered no longer valid and de-referenced. Moreover, * any pending <code>read</code> should be considered invalid. In fact, the * new producer might write some data to the internal buffer before the slice * requested by such a <code>read</code> is constructed — so the caller * could be getting the wrong data. For this reason, it's important that all * pending <code>reads</code> join back before calling the * {@link #setProducer(ByteBufferFiller) setProducer} method.</p> * <p>A final note. This class will check the amount of data written by a * producer when it declares that the end of the stream has been reached. * An exception will be thrown if that amount is not the same as the one * declared by the producer via its * {@link ByteBufferFiller#getTotalLength() getTotalLength} method.</p> * * @see org.openmicroscopy.shoola.util.concur.ByteBufferFiller * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @version 2.2 * <small> * (<b>Internal version:</b> $Revision$ $Date$) * </small> * @since OME2.2 */ public class AsyncByteBuffer { /** The internal buffer. */ private final byte[] buffer; /** * The maximum amount of bytes that the producer will be asked to write * at a time during the filling loop. */ private final int BLOCK_SIZE; /** Provides a thread to run the producer loop. */ private final CmdProcessor cmdProcessor; /** * Run in a separate thread to loop on a {@link ByteBufferFiller} * in order to write (at most) {@link #BLOCK_SIZE} bytes at a time * to {@link #buffer}. */ private ProducerLoop producer; /** * Returned by {@link #cmdProcessor} when posting a request to * execute the {@link #producer} loop. * We use it to cancel execution as well as rendezvous with the * {@link #producer}. */ private Future producerHandle; /** * Encapsulates and parameterizes the algorithm common to all read methods. * * @param offset The starting position. * @param length The length of the data segment. * @param timeout The amount of milliseconds the calling thread should be * suspended if the specified data segment is not yet * available. Pass <code>-1</code> for an unbounded wait * or <code>0</code> to not wait at all. * @return A read-only slice of the internal data buffer. * @throws BufferWriteException If an error ocurred while the producer wrote * data into the internal buffer. * @throws InterruptedException If the calling thread was interrupted. */ private ReadOnlyByteArray doRead(int offset, int length, long timeout) throws BufferWriteException, InterruptedException { ReadOnlyByteArray roba = null; ProducerLoop currentProducer; //Acquire this object's lock and get a reference to the current //producer. We need to acquire the lock in order to avoid any //overlapping with setProducer(). However, we release it right //after grabbing the reference so to allow a setProducer() call //to proceed if we have to wait for data. synchronized (this) { currentProducer = producer; } if (currentProducer != null) { boolean available = false; //Check to see if currentProducer has written [off, len] yet. //If we suspend, no deadlock is possible b/c we've already //released this object's lock -- so any blocking is not going //to affect setProducer's callers. if (timeout < 0) //Unbounded wait. IllArgExc if bad [off, len]. available = currentProducer.waitForData(offset, length); else //No wait if timeout=0 or bounded wait otherwise. available = //IllArgExc if bad [off, len]. currentProducer.waitForData(offset, length, timeout); //If a new producer's been set in the mean time and this new //producer has already written some data, then it's possible //that the returned buffer slice contain the wrong data -- //the caller is expecting data coming from currentProducer. //For this reason, it's important that all pending reads join //back before calling setProducer() -- this is the caller's //responsibility though. if (available) roba = readFromBuffer(offset, length); } return roba; } /** * Encapsulates read access to {@link #buffer} to make sure that the * most recently written data is read in. * * @param offset The starting position. * @param length The length of the data segment. * @return A read-only slice of the internal data buffer. */ private ReadOnlyByteArray readFromBuffer(int offset, int length) { synchronized (buffer) { //Refresh consumer thread's working memory. The values //last written by the producer will now be visible. return new ReadOnlyByteArray(buffer, offset, length); } } /** * Encapsulates write access to {@link #buffer} to make sure that the * written data is flushed from the producer thread's working memory. * * @param producer The producer that will be asked to write * {@link #BLOCK_SIZE} bytes into {@link #buffer}. * @param offset The starting position. * @return The write length as returned by <code>producer</code>. * @throws BufferWriteException Thrown by <code>producer</code>. */ int writeToBuffer(ByteBufferFiller producer, int offset) throws BufferWriteException { synchronized (buffer) { //Flush producer thread's working memory. Next time this lock //will be acquired, the results of the following write will be //visible to the thread acquiring the lock. return producer.write(buffer, offset, BLOCK_SIZE); } } /** * Creates a new instance. * Use the {@link #setProducer(ByteBufferFiller) setProducer} method to * specify a producer and start an asynchronous data retrieval. * The <code>size</code> parameter specifies the size of the internal byte * buffer. That is, how much space will be available to any producer. * This is not to be confused with the amount of data that a producer * can write into the buffer, that is the value returned by its * {@link ByteBufferFiller#getTotalLength() getTotalLength} method. * * @param size The the size of the internal byte buffer. Must be positive. * @param blockSize The maximum amount of bytes that the producer will * be asked to write at a time during the filling loop. * Must be positive and no greater than <code>size</code>. */ public AsyncByteBuffer(int size, int blockSize) { if (size < 1) throw new IllegalArgumentException("Non-positive size: "+size+"."); if (blockSize < 1 || size < blockSize) throw new IllegalArgumentException( "Invalid blockSize: "+blockSize+"."); buffer = new byte[size]; BLOCK_SIZE = blockSize; cmdProcessor = new AsyncProcessor(); } /** * Creates a new instance. * Use the {@link #setProducer(ByteBufferFiller) setProducer} method to * specify a producer and start an asynchronous data retrieval. * The <code>size</code> parameter specifies the size of the internal byte * buffer. That is, how much space will be available to any producer. * This is not to be confused with the amount of data that a producer * can write into the buffer, that is the value returned by its * {@link ByteBufferFiller#getTotalLength() getTotalLength} method. * * @param size The the size of the internal byte buffer. Must be positive. * @param blockSize The maximum amount of bytes that the producer will * be asked to write at a time during the filling loop. * Must be positive and no greater than <code>size</code>. * @param cp Will be used to get a thread to run the producer's loop. * Mustn't be <code>null</code>. */ public AsyncByteBuffer(int size, int blockSize, CmdProcessor cp) { if (size < 1) throw new IllegalArgumentException("Non-positive size: "+size+"."); if (blockSize < 1 || size < blockSize) throw new IllegalArgumentException( "Invalid blockSize: "+blockSize+"."); if (cp == null) throw new NullPointerException("No command processor."); buffer = new byte[size]; BLOCK_SIZE = blockSize; cmdProcessor = cp; } /** * Creates and start a new loop to allow <code>p</code> to write its * payload into the internal buffer. * The loop is run in a separate thread. * If there's already a producer running, then this method will cancel its * loop and wait for it to terminate. Then, if the passed producer is not * <code>null</code>, a new loop is created and started for it. Otherwise, * this method just returns after clearing the previous producer's loop. * Note that if there was already a producer, invoking this method will * cause the internal buffer to be refilled with new data. Because the * read methods return objects that access the internal buffer directly * (data is not copied), after calling this method, any * {@link ReadOnlyByteArray} object previously obtained from one of the * read methods should be considered no longer valid and de-referenced. * * @param p The producer that will write data into the buffer. * The amount of data that the producer can write into * the buffer, that is the value returned by * {@link ByteBufferFiller#getTotalLength()}, mustn't * exceed the size returned by {@link #getSize()}. * @throws InterruptedException If this thread was interrupted. */ public void setProducer(ByteBufferFiller p) throws InterruptedException { synchronized (this) { //Serialize calls. if (producer != null) { //Then we need to clean up. //First cancel execution. Any pending read will throw an //BufferWriteException if this succeeds while the read is //waiting for data. producerHandle.cancelExecution(); //Then rendezvous with the producer. We have to wait for the //producer to terminate before we can possibly start a new //one -- otherwise two threads would be writing to the buffer. try { producerHandle.getResult(); } catch (ExecException ee) { //Ignore, we're going to discard this producer anyway. } producer = null; //Get rid of prevoius producer. } //If p is null, then we're done. The caller just wanted to get //rid of the previous producer, if any. if (p == null) return; //We've got a new producer p, so start it's loop. producer = new ProducerLoop(this, p); producerHandle = cmdProcessor.exec(producer, producer); } } /** * Attempts to read <code>length</code> bytes from the buffer, starting from * the <code>offset</code> position. * This method will block if the requested data is not available. So the * caller is suspended until the data becomes available or an error is * detected. * The passed arguments must define an interval * <code>[offset, offset+length]</code> in <code>[0, limit]</code>, * <code>limit</code> being the amount of data that the producer can write * into the buffer, that is the value returned by its * {@link ByteBufferFiller#getTotalLength() getTotalLength} method. * * @param offset The starting position. * @param length The length of the data segment. * @return A read-only slice of the internal data buffer. * @throws BufferWriteException If an error ocurred while the producer wrote * data into the internal buffer. * @throws InterruptedException If this thread was interrupted. */ public ReadOnlyByteArray read(int offset, int length) throws BufferWriteException, InterruptedException { return doRead(offset, length, -1); //-1 for unbounded wait. } /** * Attempts to read <code>length</code> bytes from the buffer, starting from * the <code>offset</code> position and waiting at most <code>timeout</code> * milliseconds for the data to become available. * This method doesn't block forever. If the requested data is not yet * available after the <code>timeout</code> elapses, then <code>null</code> * is returned instead. * The passed arguments must define an interval * <code>[offset, offset+length]</code> in <code>[0, limit]</code>, * <code>limit</code> being the amount of data that the producer can write * into the buffer, that is the value returned by * {@link ByteBufferFiller#getTotalLength()}. * * @param offset The starting position. * @param length The length of the data segment. * @param timeout The amount of milliseconds the calling thread should be * suspended if the specified data segment is not yet * available. If the passed value is not positive, then * the caller won't be suspended at all. * @return A read-only slice of the internal data buffer. * @throws BufferWriteException If an error ocurred while the producer wrote * data into the internal buffer. * @throws InterruptedException If this thread was interrupted. */ public ReadOnlyByteArray read(int offset, int length, long timeout) throws BufferWriteException, InterruptedException { if (timeout <= 0) timeout = 0; //<0 reserved for unbounded wait. return doRead(offset, length, timeout); } /** * Returns the capacity of this buffer. * That is, the size of the internal byte buffer. This is not to be * confused with the amount of data that the producer can write into * the buffer, that is the value returned by its * {@link ByteBufferFiller#getTotalLength() getTotalLength} method. * * @return The buffer's size. */ public int getSize() { return buffer.length; } }