/*
* org.openmicroscopy.shoola.util.concur.ObjectTransfer
*
*------------------------------------------------------------------------------
* 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
/**
* Synchronously transfers an object from one thread to another.
* A thread that {@link #handOff(Object) hands} an object off has to wait until
* another thread {@link #collect() collects} it, and vice-versa.
* This class can be considered a synchronous channel basically a
* degenerated synchronous bounded buffer with no internal capacity.
*
* @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 ObjectTransfer
{
/* IMPLEMENTATION NOTE: The implementation would be much easier if we used the
* object's monitor instead of 3 semaphores. However, giving each condition
* (that a thread can await) a separate monitor leads to big savings in terms
* of notifications -- also semaphores use notify() instead of notifyAll(), so
* no extra notifications have to be sent.
*/
/** Holds the object while in transit. */
private Object objInTransit;
/**
* Represents the Empty state.
* This state is defined by the following constraints:
* <code>empty: 1, full: 0, objInTransit: null</code>.
*/
private final Semaphore empty;
/**
* Represents the Full state.
* This state is defined by the following constraints:
* <code>empty: 0, full: 1, objInTransit: !null</code>.
*/
private final Semaphore full;
/**
* Blocks a thread handing an object off until another thread collects it.
*/
private final Semaphore objTransferred;
/**
* Creates a new instance.
*/
public ObjectTransfer()
{
//Set the Empty state.
empty = new Semaphore(1);
full = new Semaphore(0);
objInTransit = null;
//No hand-off can complete until collection is done.
objTransferred = new Semaphore(0);
}
/**
* Waits until the object in transit has been collected.
* If the calling thread (producer) is interrupted, then we just record
* this event and we roll forward to guarantee that the producer eventually
* rendezvous with the thread that will collect the object (consumer). If
* we din't do this, the signal sent by the consumer thread would be picked
* by the next producer that does an hand-off assuming the caller was
* interrupted before the {@link #collect() collect} method returns in the
* consumer thread. In this case, the next hand-off is likely to proceed
* before its object is actually collected.
*
* Only call this method after the hand-off procedure has completed.
*
* @throws InterruptedException If the calling thread was interrupted.
*/
private void waitForObjectToBeTransferred()
throws InterruptedException
{
InterruptedException exc = null;
while (true) {
try {
objTransferred.down(); //See NOTE below.
break;
} catch (InterruptedException ie) { //Roll forward.
exc = ie;
}
}
if (exc != null) throw exc; //Re-throw if needed.
}
/* NOTE: Here we rely on the fact that the interrupted status is cleared
* upon throwing an InterruptedException. If Semaphore didn't do that,
* then we would end up in an infinite loop in the case of interruption.
*/
/**
* Convenience method to collapse together the implementations of the
* hand-off methods.
*
* @param x The object to transfer.
* @param timeout <code>-1</code> for unbounded wait, <code>0</code> for
* no wait, a positive value for timed wait.
* @return <code>true</code> if <code>x</code> was delivered to a consumer
* thread, <code>false</code> if <code>x</code> couldn't be put
* into the inner channel for delivery.
* @throws InterruptedException If the calling thread was interrupted.
* Note that even if the caller is interrupted, we guarantee that
* it will eventually rendezvous with a consumer thread.
*/
private boolean doHandOff(Object x, long timeout)
throws InterruptedException
{
//Never accept null. This way the timed version of collect can
//return null to mean that the timeout elapsed and no object is
//available. If we accepted null here, then returning null from
//collect would be ambiguos.
if (x == null) throw new NullPointerException("No object.");
//Timed/Unbouded/No wait until we can exit the Empty state. In order
//to start a transition out of the Empty state, the object must be in
//that state. So if the state is Full, we wait or return false --
//depending on the value of timeout.
if (timeout == -1) //-1 means unbounded wait.
empty.down();
else //Timed wait if timeout > 0 or no wait if 0.
if (!empty.down(timeout)) return false;
if (flowObs != null) flowObs.update(TRANSITION_TO_FULL); //Enable tests.
objInTransit = x;
full.up(); //Notify we've entered the Full state.
//Now wait until the object has been transferred -- that is, until
//the collect method returns.
waitForObjectToBeTransferred();
return true;
}
/**
* Convenience method to collapse together the implementations of the
* collect methods.
*
* @param timeout <code>-1</code> for unbounded wait, <code>0</code> for
* no wait, a positive value for timed wait.
* @return An object that was handed-off by a producer or <code>null</code>
* if no object was collected.
* @throws InterruptedException If the caller was interrupted.
*/
private Object doCollect(long timeout)
throws InterruptedException
{
//Timed/Unbouded/No wait until we can exit the Full state. In order
//to start a transition out of the Full state, the object must be in
//that state. So if the state is Empty, we wait or return null --
//depending on the value of timeout.
if (timeout == -1) //-1 means unbounded wait.
full.down();
else //Timed wait if timeout > 0 or no wait if 0.
if (!full.down(timeout)) return null;
if (flowObs != null) flowObs.update(TRANSITION_TO_EMPTY);//Enable tests.
Object x = objInTransit; //Collect the object.
objInTransit = null;
empty.up(); //Notify we've entered the Empty state.
objTransferred.up(); //Notify we've collected the object.
return x;
}
/**
* Attempts to transfer <code>x</code> to a consumer thread, waiting at most
* <code>timeout</code> milliseconds.
* This method does two things. First, it tries to put <code>x</code> into
* the inner delivery channel for later collection by a consumer thread. As
* only one object at a time can ever be in transit, concurrent hand-offs
* and collections will have to comptete to gain exclusive access to the
* channel. This method will wait at most <code>timeout</code> milliseconds
* to access the channel and then give up if it couldn't succeed. If it
* does succeed, then it waits until a consumer thread will collect the
* passed object. Note that this second step is an unbounded wait, so you
* have to be sure that a consumer will eventually collect the object or
* the calling thread will block forever.
*
* @param x The object to transfer. Mustn't be <code>null</code>.
* @param timeout The amount of milliseconds to wait for the channel to be
* free. A non positive value means no wait at all.
* @return <code>true</code> if <code>x</code> was delivered to a consumer
* thread, <code>false</code> if <code>x</code> couldn't be put
* into the inner channel for delivery.
* @throws InterruptedException If the calling thread was interrupted.
* Note that even if the caller is interrupted, we guarantee that
* it will eventually rendezvous with a consumer thread. That is,
* the second step detailed above is performed regardless of
* interruption.
* @see #handOff(Object)
* @see #collect()
* @see #collect(long)
*/
public boolean handOff(Object x, long timeout)
throws InterruptedException
{
if (timeout <= 0) //Caller meant no wait.
timeout = 0; //-1 is reserved for unbounded waits.
return doHandOff(x, timeout);
}
/**
* Transfers <code>x</code> to a consumer thread.
* This method does two things. First, it waits until it can put
* <code>x</code> into the inner delivery channel for later collection by a
* consumer thread. Then it waits until a consumer thread will collect the
* passed object. As only one object at a time can ever be in transit, you
* have to be sure that there are consumer threads that will eventually
* remove any object already in transit and then collect the object that
* you're handing off. Otherwise, the calling thread will block forever
* in fact, the two steps just described are unbounded waits.
*
* @param x The object to transfer. Mustn't be <code>null</code>.
* @throws InterruptedException If the calling thread was interrupted.
* Note that even if the caller is interrupted, we guarantee that
* it will eventually rendezvous with a consumer thread. That is,
* the second step detailed above is performed regardless of
* interruption.
* @see #handOff(Object, long)
* @see #collect()
* @see #collect(long)
*/
public void handOff(Object x)
throws InterruptedException
{
doHandOff(x, -1); //-1 means unbounded wait.
}
/**
* Collects an object that was handed off by a producer thread.
* This method blocks until an object is available, so you have to be sure
* that there are producer threads around before you invoke this method.
* Otherwise, the caller might block forever however, you can still
* interrupt the thread.
*
* @return An object that was handed-off by a producer.
* @throws InterruptedException If the caller was interrupted.
* @see #collect(long)
* @see #handOff(Object)
* @see #handOff(Object, long)
*/
public Object collect()
throws InterruptedException
{
return doCollect(-1); //-1 means unbounded wait.
}
/**
* Collects an object that was handed off by a producer thread.
* This method blocks until an object is available or <code>timeout</code>
* elapses.
*
* @param timeout The amount of milliseconds to wait for an object to be
* handed off by a producer. A non positive value means
* no wait at all.
* @return An object that was handed-off by a producer or <code>null</code>
* if no object was collected.
* @throws InterruptedException If the caller was interrupted.
* @see #collect()
* @see #handOff(Object)
* @see #handOff(Object, long)
*/
public Object collect(long timeout)
throws InterruptedException
{
if (timeout <= 0) //Caller meant no wait.
timeout = 0; //-1 is reserved for unbounded waits.
return doCollect(timeout);
}
/*
* ==============================================================
* Follows code to enable testing.
* ==============================================================
*/
static final int TRANSITION_TO_EMPTY = 1;
static final int TRANSITION_TO_FULL = 2;
private ControlFlowObserver flowObs;
void register(ControlFlowObserver obs) { flowObs = obs; }
Object getObjInTransit() { return objInTransit; }
}