package jmri.util;
import java.util.concurrent.*;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* This class serves as a demonstration of some good
* threading practices in JMRI, and also as a run-time test of them.
*
* It's in the java/test package tree because it's not
* something we intend to ship to users.
*
* For information on threading in JMRI, see the
* Threading doc page.
* See also the {@link ThreadingUtil} and {@lnk WaitHandler} classes along with the
* examples in their associated JUnit test classes:
* {@link ThreadingUtilTest} and {@lnk WaitHandlerTest}.
*
* @author Bob Jacobsen Copyright 2017
*/
public class ThreadingDemoAndTest {
private volatile boolean flagInterrupted1 = false;
private volatile boolean flagInterrupted2 = false;
private volatile boolean flagInterrupted3 = false;
/**
* Show the basic life-cycle of a thread
*
* - being created,
*
- being started,
*
- waiting on a lock on an object,
*
- being woken up, and
*
- terminating.
*
* Plus the synchronization needed around the wait and wake-up calls
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = {"NN_NAKED_NOTIFY"},
justification = "no actual condition being waited on before notify")
@Test
public void testThreadingLifeCycle() {
final Object lock = new Object(); // this object is the lock for the wait and notify
final Thread t = new Thread() {
@Override
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = {"WA_NOT_IN_LOOP","UW_UNCOND_WAIT"},
justification = "wait / notify on local final object")
public void run() {
try {
synchronized(lock) {
lock.wait();
}
} catch (InterruptedException e) {
}
}
};
t.setName("testThreadingLifeCycle");
t.setDaemon(true);
// confirm our understanding of the life cycle
assertEquals( Thread.State.NEW, t.getState());
t.start();
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t); }, "Got to wait state");
synchronized(lock) {
lock.notifyAll();
}
JUnitUtil.waitFor( ()->{ return t.getState().equals(Thread.State.TERMINATED); }, "Got to terminated state");
}
/**
* How one thread t2 can "join" on the ending of another thread t1
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = {"NN_NAKED_NOTIFY"},
justification = "no actual condition being waited on before notify")
@Test
public void testThreadingJoinCycle() {
final Object lock = new Object();
final Thread t1 = new Thread() {
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = {"WA_NOT_IN_LOOP","UW_UNCOND_WAIT"},
justification = "wait / notify on local final object")
@Override
public void run() {
try {
synchronized(lock) {
lock.wait();
}
} catch (InterruptedException e) {
}
}
};
t1.setName("testThreadingLifeCycle 1");
t1.setDaemon(true);
final Thread t2 = new Thread() {
@Override
public void run() {
try {
t1.join();
} catch (InterruptedException e) {
}
}
};
t2.setName("testThreadingLifeCycle 2");
t2.setDaemon(true);
// confirm our understanding of the life cycle
assertEquals( Thread.State.NEW, t1.getState());
assertEquals( Thread.State.NEW, t2.getState());
t1.start();
t2.start();
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t1); }, "Got 1 to wait state");
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t2); }, "Got 2 to wait state");
synchronized(lock) {
lock.notifyAll();
}
JUnitUtil.waitFor( ()->{ return t1.getState().equals(Thread.State.TERMINATED); }, "Got 1 to terminated state");
JUnitUtil.waitFor( ()->{ return t2.getState().equals(Thread.State.TERMINATED); }, "Got 2 to terminated state");
}
/**
* Interrupting a thread ends the current wait, but
* doesn't kill the thread; it can go on to wait more.
*/
@Test
public void testInterruptAndContinue() {
flagInterrupted1 = false; // set true when we get to the first wait
flagInterrupted2 = false; // set true when we get to the second wait
final Object lock = new Object();
final Thread t1 = new Thread() {
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = {"WA_NOT_IN_LOOP","UW_UNCOND_WAIT"},
justification = "wait / notify on local final object")
@Override
public void run() {
try {
synchronized(lock) {
lock.wait();
}
} catch (InterruptedException e) {
flagInterrupted1 = true;
// we're just going to continue to another wait
}
try {
synchronized(lock) {
lock.wait();
}
} catch (InterruptedException e) {
flagInterrupted2 = true;
// we're just going to continue and terminate
}
}
};
t1.setName("testInterruptAndContinue");
t1.setDaemon(true);
// confirm our understanding of the life cycle
assertEquals( Thread.State.NEW, t1.getState());
t1.start();
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t1); }, "Got to wait state");
t1.interrupt(); // end 1st wait
JUnitUtil.waitFor( ()->{ return flagInterrupted1; }, "handled first interrupt");
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t1); }, "and went to second wait");
t1.interrupt(); // end 2nd wait
JUnitUtil.waitFor( ()->{ return flagInterrupted2; }, "handled second interrupt");
JUnitUtil.waitFor( ()->{ return t1.getState().equals(Thread.State.TERMINATED); }, "Got 1 to terminated state");
}
/**
* Interrupting a thread that restores the interrupted status also kills the next wait
*/
@Test
public void testThreadReassertsInterrupt() {
flagInterrupted1 = false; // set true when we leave the first wait
flagInterrupted2 = false; // set true when we leave the second wait
flagInterrupted3 = false; // set true when we leave the third wait
final Object lock = new Object();
final Thread t1 = new Thread() {
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = {"WA_NOT_IN_LOOP","UW_UNCOND_WAIT"},
justification = "wait / notify on local final object")
@Override
public void run() {
try {
synchronized(lock) {
lock.wait();
}
} catch (InterruptedException e) {
flagInterrupted1 = true;
Thread.currentThread().interrupt();
// restored the status for the next wait
}
try {
synchronized(lock) {
lock.wait();
}
} catch (InterruptedException e) {
flagInterrupted2 = true;
}
try {
synchronized(lock) {
lock.wait();
}
} catch (InterruptedException e) {
flagInterrupted3 = true;
// we're just going to continue and terminate
}
}
};
t1.setName("testThreadReassertsInterrupt");
t1.setDaemon(true);
// confirm our understanding of the life cycle
assertEquals( Thread.State.NEW, t1.getState());
t1.start();
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t1); }, "Got to wait state");
t1.interrupt(); // end 1st wait
JUnitUtil.waitFor( ()->{ return flagInterrupted1; }, "handled first interrupt");
JUnitUtil.waitFor( ()->{ return flagInterrupted2; }, "continued to second interrupt handler");
// it ran to the 2nd, but waited there because that consumed the interrupt
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t1); }, "at third wait");
assertFalse( flagInterrupted3);
t1.interrupt(); // end 3rd wait
JUnitUtil.waitFor( ()->{ return flagInterrupted3; }, "continued to third interrupt handler");
JUnitUtil.waitFor( ()->{ return t1.getState().equals(Thread.State.TERMINATED); }, "continued off the end to terminated state");
}
/**
* Confirm interrupt behavior of blocking queue put
*/
@Test
public void testInterruptBlockingQueuePut() {
flagInterrupted1 = false; // set true when we leave the first wait
flagInterrupted2 = false; // set true when we leave the second wait
flagInterrupted3 = false; // set true when we leave the third wait
BlockingQueue q = new ArrayBlockingQueue<>(2);
final Thread t = new Thread() {
@Override
public void run() {
try {
q.put(1);
q.put(2);
} catch (InterruptedException e) {
fail("did not expect interrupt");
}
flagInterrupted1 = true;
// third should block until read
try {
q.put(3);
} catch (InterruptedException e) {
// just eat and continue
flagInterrupted2 = true;
}
try {
q.put(4);
} catch (InterruptedException e) {
// just eat and continue
flagInterrupted3 = true;
}
try {
q.put(5);
} catch (InterruptedException e) {
// just eat and continue
flagInterrupted3 = true;
}
}
};
t.setName("testInterruptBlockingQueuePut");
t.setDaemon(true);
// confirm our understanding of the life cycle
assertEquals( Thread.State.NEW, t.getState());
t.start();
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t); }, "Got to wait state after adding 2");
assertTrue(flagInterrupted1);
assertEquals( 2, q.size());
assertFalse( flagInterrupted2);
assertEquals( Integer.valueOf(1), q.poll(), "first");
// should have allowed another
JUnitUtil.waitFor( ()->{ return q.size() == 2; }, "Third added");
assertFalse( flagInterrupted2);
assertFalse( flagInterrupted3);
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t); }, "Got to wait state after adding 3");
JUnitUtil.waitFor( ()->{ return q.size() == 2; }, "Fourth not yet present");
// waiting to add 4, interrupt
t.interrupt();
JUnitUtil.waitFor( ()->{ return flagInterrupted3; }, "Interrupt handled in add 4");
// pull contents
assertEquals( Integer.valueOf(2), q.poll(), "second");
assertEquals( Integer.valueOf(3), q.poll(), "third");
JUnitUtil.waitFor( ()->{ return t.getState().equals(Thread.State.TERMINATED); }, "Got to terminated state");
assertEquals( Integer.valueOf(5), q.poll(), "fifth; fourth cancelled");
}
/**
* Confirm interrupt behavior of blocking queue get
*/
@Test
public void testInterruptBlockingQueueGet() {
flagInterrupted1 = false; // set true when we leave the first wait
flagInterrupted2 = false; // set true when we leave the second wait
flagInterrupted3 = false; // set true when we leave the third wait
BlockingQueue q = new ArrayBlockingQueue<>(2);
final Thread t = new Thread() {
@Override
public void run() {
try {
flagInterrupted1 = true;
fail(" did not expect to complete: "+q.take());
} catch (InterruptedException e) {
flagInterrupted2 = true;
}
flagInterrupted3 = true;
}
};
t.setName("testInterruptBlockingQueueGet");
t.setDaemon(true);
// confirm our understanding of the life cycle
assertEquals( Thread.State.NEW, t.getState());
t.start();
JUnitUtil.waitFor( ()->{ return ThreadingUtil.isThreadWaiting(t); }, "Got to wait input");
assertTrue(flagInterrupted1);
assertFalse( flagInterrupted2);
assertFalse( flagInterrupted2);
t.interrupt();
JUnitUtil.waitFor( ()->{ return flagInterrupted2; }, "Interrupt handled");
JUnitUtil.waitFor( ()->{ return t.getState().equals(Thread.State.TERMINATED); }, "Got to terminated state");
assertTrue(flagInterrupted3);
}
@BeforeEach
public void setUp() {
jmri.util.JUnitUtil.setUp();
}
@AfterEach
public void tearDown() {
jmri.util.JUnitUtil.tearDown();
}
}