/*
* org.openmicroscopy.shoola.util.concur.ThreadSupport
*
*------------------------------------------------------------------------------
* 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
/**
* Helps to simulate concurrent access to an object in order to verify
* synchronization and state dependence.
* <p>This class allows you to {@link #pauseMainFlow() pause} the main flow (the
* thread that creates an instance of this class, it should be the JUnit thread)
* and, meanwhile, have an alternate thread perform some other action by
* calling {@link #startAltFlow()}. The main flow should then
* {@link #awaitAltFlow() wait} for the alternate flow to terminate so that no
* alternate thread is kicking around when JUnit runs the next test this
* avoids the possibility of unplanned concurrent access.<br>
* Also static methods are provided to run a task in a separate thread but
* synchronously with respect to the calling thread that is, the caller
* will wait until the task has finished running.</p>
* <p>This class factors out some test code common to all test cases in
* <code>concur</code> and its sub-packages. Tests that verify synchronization
* mainly use this class to have the JUnit thread acquire an object's lock and
* then wait for the alternate thread to attempt to acquire the same lock
* so that we can simulate concurrent access and test synchronized blocks.
* Those tests are written against the methods of the target class (the class
* under test) that perform atomic state transitions by acquiring the target's
* lock (target is an instance of the target class), modifying the target's
* state, and then releasing the lock. Here is the common pattern used in those
* test cases (see {@link Semaphore} and {@link TestSemaphoreSync} for example):
* </p>
* <ul>
* <li>The target class allows to register a {@link ControlFlowObserver} and
* notifies it whenever entering a method that performs an atomic state
* transition. The {@link ControlFlowObserver#update(int) update} method is
* called after the lock is acquired but before the target's state is modified
* and the lock is released.</li>
* <li>The test case registers a {@link ControlFlowObserver} with the target.
* The implementation of the {@link ControlFlowObserver#update(int) update}
* method <i>first</i> starts the alternate flow and <i>then</i> pauses the
* main flow.</li>
* <li>The alternate flow attempts to get the target's state. The target
* protects access to its state with the same lock.</li>
* <li>The test consists in calling a target's method <code>m</code> that
* enforces an atomic state transition, waiting for the alternate flow to
* terminate, and then checking that the state read by the alternate flow is
* the state expected at the end of the transition performed by
* <code>m()</code> in the main flow.</li>
* </ul>
* <p>Note the sequence of events: <code>m()</code> is called in the main flow
* (JUnit), <code>update()</code> is called in the main flow too just after
* <code>m()</code> has acquired the lock, within <code>update()</code> the
* alternate flow is started and then the main flow is paused. Before it can
* access the target's state, the alternate flow has to wait until the main flow
* releases the lock. So if the state that we get is not the state in which the
* target should be after <code>m()</code>, then we can conclude that the
* alternate flow was allowed to read the state while it was modified by
* <code>m()</code> so <code>m()</code> is not acquiring the lock
* correctly. Sadly enough, even if we get the expected state, we can't assert
* <code>m()</code> is acquiring the lock properly. In fact, the alternate flow
* could have accessed the state after <code>m()</code> returned and no
* concurrent access to the target could have taken place at all. To mitigate
* this state of affairs, we use a reasonably long {@link #PAUSE_DELAY pause}
* delay for the main thread so that, in practice, the alternate flow is
* extremely likely to run while the main flow is paused and still holding the
* lock. Thus, it's <i>reasonable</i> (but not logically sound) to assume that
* <code>m()</code> was implemented correctly if we get the expected state.</p>
* <p>Tests that verify the correct behavior of state dependent actions also
* follow a common pattern. Those tests are written against the methods of the
* target class that perform actions depending on the current target's state.
* These methods are usually structured as follows: the target's lock is
* acquired, the state is checked and if some condition is satisfied the method
* proceeds (reading or modifying the state) otherwise the caller is suspended
* (by calling the {@link Object#wait() wait} method, which releases the lock)
* until that condition is satisfied. Here is the common pattern used in those
* test cases (see {@link Semaphore} and {@link TestSemaphoreStateDep} for
* example):</p>
* <ul>
* <li>The target class allows to register a {@link ControlFlowObserver} and
* notifies it whenever entering a state dependent method. The
* {@link ControlFlowObserver#update(int) update} method is called after the
* lock is acquired but before the {@link Object#wait() wait} method is
* called.</li>
* <li>The test case registers a {@link ControlFlowObserver} with the target.
* The implementation of the {@link ControlFlowObserver#update(int) update}
* method starts the alternate flow.</li>
* <li>The alternate flow has the target transition to a state in which a
* given state dependent method <code>m</code> can proceed. Note that the
* target's methods invoked by the alternate flow have to acquire the target's
* lock and issue notifications of state change.</li>
* <li>The test consists transitioning the target to a state in which
* <code>m()</code> can't proceed, invoking <code>m()</code>, waiting for the
* alternate flow to terminate, and finally verifying that <code>m()</code>
* operated on the right state.</li>
* </ul>
* <p>Note the sequence of events: <code>m()</code> is called in the main flow
* (JUnit), <code>update()</code> is called in the main flow too just after
* <code>m()</code> has acquired the lock, within <code>update()</code> the
* alternate flow is started. Before it can access the target's state, the
* alternate flow has to wait until the main flow releases the lock. Now if
* <code>m()</code> proceeds with its action and then releases the lock, it will
* have operated on the wrong state and we fail the test. Instead, if
* <code>m()</code> calls <code>wait()</code> then the lock is released, the
* alternate flow transitions the object to a suitable state, and then a
* notification is issued so that the main flow wakes up and <code>m()</code>
* proceeds with its action, this time operating on the correct state in
* this case the test succeeds. Note that if the methods invoked by the
* alternate flow fail to send the wake up signal, we get a deadlock. This is a
* failure as well, but can't be detected by JUint and we'll have to stop
* execution manually.</p>
* <p>Finally, tests that verify how an object reacts to interruption follow a
* common pattern too (see {@link Semaphore} and {@link TestSemaphoreInt} for
* example). Each test spawns an ad-hoc thread (using the static methods
* provided by this class) which is interrupted to verify how a given object
* reacts to interruption. The result of the test (usually an instance of
* {@link InterruptedException}) is passed back into the main flow (JUnit) so
* to perform the needed assertions.<br>
* Note that we never interrupt JUnit. Spawning another thread might seem
* unecessary when we could just interrupt JUnit and then clear the interrupted
* status after the test. However implementation characteristics of
* interruption-based methods are quite uncertain, so we prefer using a
* separate thread (which is simply discarded at the end of the test) rather
* than interrupting JUnit and making thus room for possible side effects.
* </p>
*
* @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 ThreadSupport
{
/** The delay used to pause the main thread. */
public static final int PAUSE_DELAY = 2000;
/**
* Runs <code>task</code> <i>synchronously</i> but in a separate thread.
* This means that the main flow (the thread in which this method is
* invoked, should be JUnit) will wait until the <code>task</code> has
* finished running. Interrupting the main flow will buy you a nice
* {@link Error}.
*
* @param task The task to run. Mustn't be <code>null</code>.
*/
public static void runInNewThread(Runnable task)
{
if (task == null) throw new NullPointerException("No task.");
Thread t = new Thread(task);
t.start();
try {
t.join();
} catch (InterruptedException ie) {
throw new Error("Main flow thread was interrupted.");
}
}
/**
* Runs <code>task</code> <i>synchronously</i> but in a separate thread,
* which is interrupted just before calling <code>task.run()</code>.
* The main flow (the thread in which this method is invoked, should be
* JUnit) will wait until the <code>task</code> has finished running.
* Interrupting the main flow will buy you a nice {@link Error}.
*
* @param task The task to run. Mustn't be <code>null</code>.
*/
public static void runInNewInterruptedThread(final Runnable task)
{
if (task == null) throw new NullPointerException("No task.");
Thread t = new Thread(new Runnable() {
public void run() {
Thread.currentThread().interrupt();
task.run();
}
});
t.start();
try {
t.join();
} catch (InterruptedException ie) {
throw new Error("Main flow thread was interrupted.");
}
}
/** The thread that creates this object. */
private final Thread mainFlow;
/** The thread that we spawn to perform the alternate flow. */
private final Thread altFlow;
/**
* Creates a new object.
* The main flow is assumed to be the thread in which this constructor is
* invoked, so you should create this object within the JUnit thread
* (usually within the <code>setUp</code> method). The passed argument
* specifies the actions to be carried out by the alternate flow. This
* thread is automatically created for you, but only started when you
* call {@link #startAltFlow()}.
*
* @param altFlow The activity to be performed by the alternate thread.
*/
public ThreadSupport(Runnable altFlow)
{
mainFlow = Thread.currentThread(); //Should be JUnit.
this.altFlow = new Thread(altFlow);
}
/**
* Starts the alternate thread.
*/
public void startAltFlow()
{
altFlow.start();
}
/**
* Puts the main flow to sleep for {@link #PAUSE_DELAY} milliseconds.
* You must invoke this method within the main flow, otherwise an
* {@link Error} will be thrown. You get the same treat, if you interrupt
* the main flow while it's sleeping.
*/
public void pauseMainFlow()
{
if (Thread.currentThread() != mainFlow)
throw new Error("This method must be invoked in the main flow.");
try {
Thread.sleep(PAUSE_DELAY);
} catch (InterruptedException ie) {
throw new Error("Main flow thread was interrupted.");
}
}
/**
* Tells whether the current thread is the main flow.
*
* @return <code>true</code> if main flow, <code>false</code> otherwise.
*/
public boolean isMainFlow()
{
return Thread.currentThread() == mainFlow;
}
/**
* Tells whether the current thread is the alternate flow.
*
* @return <code>true</code> if alternate flow,
* <code>false</code> otherwise.
*/
public boolean isAltFlow()
{
return Thread.currentThread() == altFlow;
}
/**
* Interrupts the alternate flow.
*/
public void interruptAltFlow()
{
altFlow.interrupt();
}
/**
* Waits for the alternate flow to terminate.
* Obviously, you're not going to call this from the alternate flow
* or you get a friendly {@link Error}.
*/
public void awaitAltFlow()
{
if (Thread.currentThread() == altFlow)
throw new Error("This method mustn't be invoked in the alt flow.");
try {
altFlow.join();
} catch (InterruptedException ie) {
throw new Error("Interrupted wait for the alt flow to join.");
}
}
}