blob: 1c242dd570e0f3cff8fd70e83b7b3e818482c155 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2010 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.tests.internal.synchronizers;
import org.eclipse.jpt.utility.Command;
import org.eclipse.jpt.utility.internal.CompositeException;
import org.eclipse.jpt.utility.internal.SynchronizedBoolean;
import org.eclipse.jpt.utility.internal.synchronizers.SynchronousSynchronizer;
import org.eclipse.jpt.utility.synchronizers.Synchronizer;
import org.eclipse.jpt.utility.tests.internal.MultiThreadedTestCase;
import org.eclipse.jpt.utility.tests.internal.TestTools;
@SuppressWarnings("nls")
public class SynchronousSynchronizerTests
extends MultiThreadedTestCase
{
PrimaryModel1 primaryModel1;
SecondaryModel1 secondaryModel1;
Command command1;
Synchronizer synchronizer1;
PrimaryModel2 primaryModel2;
SecondaryModel2 secondaryModel2;
Command command2;
Synchronizer synchronizer2;
public SynchronousSynchronizerTests(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
super.setUp();
this.primaryModel1 = new PrimaryModel1();
this.secondaryModel1 = new SecondaryModel1(this.primaryModel1);
this.command1 = new SynchronizeSecondaryModelCommand1(this.secondaryModel1);
this.synchronizer1 = new SynchronousSynchronizer(this.command1);
this.primaryModel1.setSynchronizer(this.synchronizer1);
this.primaryModel2 = new PrimaryModel2();
this.secondaryModel2 = new SecondaryModel2(this.primaryModel2);
this.command2 = new SynchronizeSecondaryModelCommand2(this.primaryModel2, this.secondaryModel2);
this.synchronizer2 = new SynchronousSynchronizer(this.command2);
this.primaryModel2.setSynchronizer(this.synchronizer2);
}
public void testInitialization() {
assertEquals(4, this.secondaryModel1.getDoubleCount());
}
public void testToString() {
assertNotNull(this.synchronizer1.toString());
}
public void testChange() {
assertEquals(4, this.secondaryModel1.getDoubleCount());
this.primaryModel1.setCount(7);
assertEquals(14, this.secondaryModel1.getDoubleCount());
}
public void testStart() {
assertEquals(4, this.secondaryModel1.getDoubleCount());
this.primaryModel1.setSynchronizer(Synchronizer.Null.instance());
this.primaryModel1.setCount(7);
assertEquals(4, this.secondaryModel1.getDoubleCount());
this.primaryModel1.setSynchronizer(this.synchronizer1);
assertEquals(14, this.secondaryModel1.getDoubleCount());
}
public void testStop() {
assertEquals(4, this.secondaryModel1.getDoubleCount());
this.primaryModel1.dispose();
this.primaryModel1.setCount(7);
assertEquals(4, this.secondaryModel1.getDoubleCount());
}
public void testDoubleStart() {
assertEquals(4, this.secondaryModel1.getDoubleCount());
boolean exCaught = false;
try {
this.primaryModel1.startSynchronizer();
fail();
} catch (IllegalStateException ex) {
exCaught = true;
}
assertTrue(exCaught);
this.primaryModel1.setCount(7);
assertEquals(14, this.secondaryModel1.getDoubleCount());
}
public void testDoubleStop() {
assertEquals(4, this.secondaryModel1.getDoubleCount());
this.primaryModel1.dispose();
boolean exCaught = false;
try {
this.primaryModel1.dispose();
fail();
} catch (IllegalStateException ex) {
exCaught = true;
}
assertTrue(exCaught);
this.primaryModel1.setCount(7);
assertEquals(4, this.secondaryModel1.getDoubleCount());
}
public void testRecursiveChange() {
assertEquals(4, this.secondaryModel2.getDoubleCount());
this.primaryModel2.setCount(7);
assertEquals(10, this.primaryModel2.getCountPlus3());
assertEquals(14, this.secondaryModel2.getDoubleCount());
assertEquals(20, this.secondaryModel2.getDoubleCountPlus3());
}
/**
* Call #stop() from another thread.
* Verify that no further synchronizations occur.
* Verify the call to #stop() does not return until the pending
* synchronization is complete (must set {@link #DEBUG} to true and
* look at the console).
*
* ticks:
* 0 - start "sync" thread (which will sleep for 1 tick);
* start "stop" thread (which will sleep for 2 ticks)
* 1 - "sync" thread wakes up and calls PrimaryModel1.setCount(int)
* which triggers call to Synchronizer.synchronize() to begin
* synchronization of SecondaryModel (which will run for 2 ticks;
* i.e. it will finish on tick 3)
* 2 - "stop" thread wakes up and calls Synchronizer.stop() and should
* wait until synchronization is complete
* 3 - synchronization completes first execution and should stop;
* "stop" thread should run to completion once the synchronization has stopped
*/
public void testCallStopFromAnotherThread() throws Exception {
log("=====" + this.getName() + "=====");
PrimaryModel2 primaryModel3 = new PrimaryModel2();
// a synchronize will occur here:
SecondaryModel3 secondaryModel3 = new SecondaryModel3(primaryModel3);
Command command3 = new SynchronizeSecondaryModelCommand2(primaryModel3, secondaryModel3);
Synchronizer synchronizer3 = new SynchronousSynchronizer(command3);
// another synchronize will occur here:
primaryModel3.setSynchronizer(synchronizer3);
secondaryModel3.setTicks(2);
assertEquals(2, primaryModel3.getCount());
assertEquals(5, primaryModel3.getCountPlus3());
assertEquals(4, secondaryModel3.getDoubleCount());
assertEquals(10, secondaryModel3.getDoubleCountPlus3());
Thread syncThread = this.buildTriggerSynchronizeThread(primaryModel3, 1);
Thread stopThread = this.buildStopThread(synchronizer3, 2);
log("ALL threads start");
stopThread.start();
syncThread.start();
stopThread.join();
syncThread.join();
// 'doubleCount' is synchronized; but the synchronization is stopped
// while that is happening (by the 'stopThread'), so 'doubleCountPlus3'
// does not get synchronized
assertEquals(7, primaryModel3.getCount());
assertEquals(10, primaryModel3.getCountPlus3());
assertEquals(14, secondaryModel3.getDoubleCount());
// this does not get updated because it would've been updated by the
// recursive call to #synchronize(), but by that time #stop() had been called
assertEquals(10, secondaryModel3.getDoubleCountPlus3());
}
private Thread buildTriggerSynchronizeThread(PrimaryModel2 primaryModel, long ticks) {
return this.buildThread(this.buildTriggerSynchronizeRunnable(primaryModel, ticks), "trigger sync");
}
private Runnable buildTriggerSynchronizeRunnable(final PrimaryModel2 primaryModel, final long ticks) {
return new Runnable() {
public void run() {
TestTools.sleep(ticks * TICK);
primaryModel.setCount(7);
}
};
}
private Thread buildStopThread(Synchronizer synchronizer, long ticks) {
return this.buildThread(this.buildStopRunnable(synchronizer, ticks), "stop");
}
private Runnable buildStopRunnable(final Synchronizer synchronizer, final long ticks) {
return new Runnable() {
public void run() {
TestTools.sleep(ticks * TICK);
log("STOP thread Synchronizer.stop()");
synchronizer.stop();
log("STOP thread stop");
}
};
}
/**
* Code cloned from {@link #testStopCalledFromAnotherThread()}.
*
* Interrupt the "stop" thread while it is waiting for the "synch" thread to finish.
* Verify that no further synchronizations occur.
* Verify the call to #stop() returns *before*
* synchronization is complete (must set {@link #DEBUG} to true and
* look at the console).
*
* ticks:
* 0 - start "sync" thread (which will sleep for 1 tick);
* start "stop" thread (which will sleep for 2 ticks);
* start "interrupt" thread (which will sleep for 3 ticks)
* 1 - "sync" thread wakes up and calls PrimaryModel1.setCount(int)
* which triggers call to Synchronizer.synchronize() to begin
* synchronization of SecondaryModel (which will run for 3 ticks;
* i.e. it will finish on tick 4)
* 2 - "stop" thread wakes up and calls Synchronizer.stop() and should
* wait until it is interrupted
* 3 - "interrupt" thread wakes up and interrupts "stop" thread;
* both will run to completion at that point
* 4 - synchronization completes first execution and should stop since the
* "stop" thread, before it was interrupted, told the "synch" thread to stop
*/
public void testInterruptStopThread() throws Exception {
log("=====" + this.getName() + "=====");
PrimaryModel2 primaryModel3 = new PrimaryModel2();
// a synchronize will occur here:
SecondaryModel3 secondaryModel3 = new SecondaryModel3(primaryModel3);
Command command3 = new SynchronizeSecondaryModelCommand2(primaryModel3, secondaryModel3);
Synchronizer synchronizer3 = new SynchronousSynchronizer(command3);
// another synchronize will occur here:
primaryModel3.setSynchronizer(synchronizer3);
secondaryModel3.setTicks(3);
assertEquals(2, primaryModel3.getCount());
assertEquals(5, primaryModel3.getCountPlus3());
assertEquals(4, secondaryModel3.getDoubleCount());
assertEquals(10, secondaryModel3.getDoubleCountPlus3());
Thread syncThread = this.buildTriggerSynchronizeThread(primaryModel3, 1);
Thread stopThread = this.buildStopThread(synchronizer3, 2);
Thread interruptThread = this.buildInterruptThread(stopThread, 3);
log("ALL threads start");
stopThread.start();
syncThread.start();
interruptThread.start();
stopThread.join();
syncThread.join();
interruptThread.join();
// 'doubleCount' is synchronized; but the synchronization is stopped
// while that is happening (by the 'stopThread'), so 'doubleCountPlus3'
// does not get synchronized
assertEquals(7, primaryModel3.getCount());
assertEquals(10, primaryModel3.getCountPlus3());
assertEquals(14, secondaryModel3.getDoubleCount());
// this does not get updated because it would've been updated by the
// recursive call to #synchronize(), but by that time #stop() had been called
assertEquals(10, secondaryModel3.getDoubleCountPlus3());
}
/**
* Code cloned from {@link #testStopCalledFromAnotherThread()}.
*
* Interrupt the "sync" thread while it is synchronizing, cutting short the synchronization.
* Verify that no further synchronizations occur.
* Verify the call to #stop() does not return until the pending
* synchronization is stops after it is interrupted (must set {@link #DEBUG} to true and
* look at the console).
*
* ticks:
* 0 - start "sync" thread (which will sleep for 1 tick)
* start "stop" thread (which will sleep for 2 ticks)
* start "interrupt" thread (which will sleep for 3 ticks)
* 1 - "sync" thread wakes up and calls PrimaryModel1.setCount(int)
* which triggers call to Synchronizer.synchronize() to begin
* synchronization of SecondaryModel (which will run for 4 ticks;
* i.e. it will finish on tick 5)
* 2 - "stop" thread wakes up and calls Synchronizer.stop() and should
* wait until the synchronization is interrupted
* 3 - "interrupt" thread wakes up and interrupts "sync" thread;
* the "interrupt" thread runs to completion, while the "sync" thread keeps
* going for another tick before it stops in "mid-synchronize"
* 4 - the "sync" thread acknowledges the interrupt and stops in
* "mid-synchronize"; it will stop since the "stop" thread told it to at tick 2;
* the "sync" and "stop" threads run to completion
*/
public void testInterruptSyncThread() throws Exception {
log("=====" + this.getName() + "=====");
PrimaryModel2 primaryModel3 = new PrimaryModel2();
// a synchronize will occur here:
SecondaryModel3 secondaryModel3 = new SecondaryModel3(primaryModel3);
Command command3 = new SynchronizeSecondaryModelCommand2(primaryModel3, secondaryModel3);
Synchronizer synchronizer3 = new SynchronousSynchronizer(command3);
// another synchronize will occur here:
primaryModel3.setSynchronizer(synchronizer3);
secondaryModel3.setTicks(4);
assertEquals(2, primaryModel3.getCount());
assertEquals(5, primaryModel3.getCountPlus3());
assertEquals(4, secondaryModel3.getDoubleCount());
assertEquals(10, secondaryModel3.getDoubleCountPlus3());
Thread syncThread = this.buildTriggerSynchronizeThread(primaryModel3, 1);
Thread stopThread = this.buildStopThread(synchronizer3, 2);
Thread interruptThread = this.buildInterruptThread(syncThread, 3);
log("ALL threads start");
stopThread.start();
syncThread.start();
interruptThread.start();
stopThread.join();
syncThread.join();
interruptThread.join();
assertEquals(7, primaryModel3.getCount());
assertEquals(10, primaryModel3.getCountPlus3());
// none of the secondary model is synchronized because the synchronize()
// method was interrupted before any synchronization had occurred
assertEquals(4, secondaryModel3.getDoubleCount());
assertEquals(10, secondaryModel3.getDoubleCountPlus3());
}
private Thread buildInterruptThread(Thread thread, long ticks) {
return this.buildThread(this.buildInterruptRunnable(thread, ticks), "interrupt");
}
private Runnable buildInterruptRunnable(final Thread thread, final long ticks) {
return new Runnable() {
public void run() {
TestTools.sleep(ticks * TICK);
log("INTERRUPT thread Thread.interrupt()");
thread.interrupt();
}
};
}
/**
* Call #stop() during a long-running "synchronize"; then call #start()
* while the #stop() is waiting for the "synchronize" to complete.
*
* ticks:
* 0 - start "sync" thread (which will sleep for 1 tick)
* start "stop" thread (which will sleep for 2 ticks)
* start "start" thread (which will sleep for 3 ticks)
* 1 - "sync" thread wakes up and calls Synchronizer.synchronize() to begin
* synchronization (which will run for 3 ticks; i.e. it will finish on tick 4)
* 2 - "stop" thread wakes up and calls Synchronizer.stop() and should
* wait until the synchronization is finished (i.e. tick 4)
* 3 - "start" thread wakes up and calls Synchronizer.start()
* which will throw an exception
* 4 - the "sync" thread finishes execution;
* it will stop since the "stop" thread told it to at tick 2;
* the "sync" and "stop" threads run to completion
*/
public void testCallStartDuringStop() throws Exception {
log("=====" + this.getName() + "=====");
DelayCommand command = this.buildDelayCommand(3);
Synchronizer synchronizer = new SynchronousSynchronizer(command);
synchronizer.start();
Thread syncThread = this.buildSynchronizeThread(synchronizer, 1);
Thread stopThread = this.buildStopThread(synchronizer, 2);
SynchronizedBoolean exCaught = new SynchronizedBoolean(false);
Thread startThread = this.buildStartThread(synchronizer, 3, exCaught);
log("ALL threads start");
syncThread.start();
stopThread.start();
startThread.start();
syncThread.join();
stopThread.join();
startThread.join();
assertTrue(exCaught.getValue());
assertEquals(2, command.count);
}
private DelayCommand buildDelayCommand(int ticks) {
return new DelayCommand(ticks);
}
class DelayCommand implements Command {
final long ticks;
boolean first = true;
int count = 0;
DelayCommand(int ticks) {
super();
this.ticks = ticks;
}
public void execute() {
this.count++;
// do nothing for the first call (from #start())
if (this.first) {
log("EXEC first");
this.first = false;
return;
}
log("EXEC start " + this.count);
TestTools.sleep(this.ticks * TICK);
log("EXEC stop " + this.count);
}
}
private Thread buildStartThread(Synchronizer synchronizer, long ticks, SynchronizedBoolean exCaught) {
return this.buildThread(this.buildStartRunnable(synchronizer, ticks, exCaught), "start");
}
private Runnable buildStartRunnable(final Synchronizer synchronizer, final long ticks, final SynchronizedBoolean exCaught) {
return new Runnable() {
public void run() {
TestTools.sleep(ticks * TICK);
log("START thread Synchronizer.start()");
try {
synchronizer.start();
} catch (IllegalStateException ex) {
exCaught.setTrue();
log("START thread exception");
}
log("START thread stop");
}
};
}
private Thread buildSynchronizeThread(Synchronizer synchronizer, long ticks) {
return this.buildThread(this.buildSynchronizeRunnable(synchronizer, ticks), "synchronize");
}
private Runnable buildSynchronizeRunnable(final Synchronizer synchronizer, final long ticks) {
return new Runnable() {
public void run() {
TestTools.sleep(ticks * TICK);
log("SYNC thread Synchronizer.synchronize()");
synchronizer.synchronize();
log("SYNC thread stop");
}
};
}
public void testException() throws Exception {
BogusCommand command = new BogusCommand();
Synchronizer synchronizer = new SynchronousSynchronizer(command);
synchronizer.start();
try {
synchronizer.synchronize();
} catch (NullPointerException ex) {
// ignore
}
boolean exCaught = false;
try {
// we used to hang here, before we began handling exceptions
synchronizer.stop();
fail();
} catch (CompositeException ex) {
assertEquals(1, ex.getExceptions().length);
exCaught = true;
}
assertTrue(exCaught);
// start + synchronize
assertEquals(2, command.count);
}
public class BogusCommand implements Command {
int count = 0;
public void execute() {
this.count++;
if (this.count > 1) {
throw new NullPointerException();
}
}
}
// ********** synchronize commands **********
public static class SynchronizeSecondaryModelCommand1 implements Command {
private final SecondaryModel1 secondaryModel;
public SynchronizeSecondaryModelCommand1(SecondaryModel1 secondaryModel) {
super();
this.secondaryModel = secondaryModel;
}
public void execute() {
this.secondaryModel.synchronize();
}
}
/**
* the primary model (subclass) has to synchronize with itself (superclass)
*/
public static class SynchronizeSecondaryModelCommand2 extends SynchronizeSecondaryModelCommand1 {
private final PrimaryModel2 primaryModel;
public SynchronizeSecondaryModelCommand2(PrimaryModel2 primaryModel, SecondaryModel2 secondaryModel) {
super(secondaryModel);
this.primaryModel = primaryModel;
}
@Override
public void execute() {
super.execute();
this.primaryModel.synchronize();
}
}
// ********** primary models **********
/**
* this object will call the synchronizer whenever its count changes,
* allowing interested parties to synchronize with the change
*/
public static class PrimaryModel1 {
protected Synchronizer synchronizer;
protected int count = 2;
public PrimaryModel1() {
super();
this.setSynchronizer_(Synchronizer.Null.instance());
}
public int getCount() {
return this.count;
}
public void setCount(int count) {
if (count != this.count) {
this.count = count;
this.stateChanged();
}
}
protected void stateChanged() {
this.synchronizer.synchronize();
}
public void setSynchronizer(Synchronizer synchronizer) {
if (synchronizer == null) {
throw new NullPointerException();
}
this.synchronizer.stop();
this.setSynchronizer_(synchronizer);
}
protected void setSynchronizer_(Synchronizer synchronizer) {
this.synchronizer = synchronizer;
this.synchronizer.start();
}
public void startSynchronizer() {
this.synchronizer.start(); // this should cause an exception
}
public void dispose() {
this.synchronizer.stop();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName());
sb.append('(');
this.toString(sb);
sb.append(')');
return sb.toString();
}
public void toString(StringBuilder sb) {
sb.append("count=");
sb.append(this.count);
}
}
/**
* This model synchronizes with itself, triggering a recursive synchronization
* with the change. Whenever the [inherited] 'count' changes, 'countPlus3'
* is updated appropriately and another synchronize is initiated if appropriate.
*/
public static class PrimaryModel2 extends PrimaryModel1 {
private int countPlus3 = 0;
public PrimaryModel2() {
super();
this.countPlus3 = this.count + 3;
}
public int getCountPlus3() {
return this.countPlus3;
}
protected void setCountPlus3(int countPlus3) {
if (countPlus3 != this.countPlus3) {
this.countPlus3 = countPlus3;
this.stateChanged();
}
}
// synchronize with itself, so to speak
public void synchronize() {
this.setCountPlus3(this.count + 3);
}
@Override
public void toString(StringBuilder sb) {
super.toString(sb);
sb.append(", countPlus3=");
sb.append(this.countPlus3);
}
}
// ********** secondary models **********
/**
* This dependent object updates its 'doubleCount' whenever the
* PrimaryModel1's 'count' changes, via the 'synchronizer'.
*/
public static class SecondaryModel1 {
protected final PrimaryModel1 primaryModel;
protected int doubleCount = 0;
public SecondaryModel1(PrimaryModel1 primaryModel) {
super();
this.primaryModel = primaryModel;
this.synchronize();
}
public void synchronize() {
this.doubleCount = this.primaryModel.getCount() * 2;
}
public int getDoubleCount() {
return this.doubleCount;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName());
sb.append('(');
this.toString(sb);
sb.append(')');
return sb.toString();
}
public void toString(StringBuilder sb) {
sb.append("doubleCount=");
sb.append(this.doubleCount);
}
}
public static class SecondaryModel2 extends SecondaryModel1 {
private int doubleCountPlus3 = 0;
public SecondaryModel2(PrimaryModel2 extendedPrimaryModel) {
super(extendedPrimaryModel);
}
protected PrimaryModel2 getExtendedPrimaryModel() {
return (PrimaryModel2) this.primaryModel;
}
@Override
public void synchronize() {
super.synchronize();
int temp = this.getExtendedPrimaryModel().getCountPlus3() * 2;
if (this.doubleCountPlus3 != temp) {
this.doubleCountPlus3 = temp;
}
}
public int getDoubleCountPlus3() {
return this.doubleCountPlus3;
}
@Override
public void toString(StringBuilder sb) {
super.toString(sb);
sb.append(", doubleCountPlus3=");
sb.append(this.doubleCountPlus3);
}
}
/**
* Allow the time required to synchronize to be specified.
* A (non-delayed) synchronize will execute before we have a chance to
* change the tick count. (It is called in the SecondaryModel1 constructor.)
*/
public static class SecondaryModel3 extends SecondaryModel2 {
private long delay = 0;
public SecondaryModel3(PrimaryModel2 primaryModel) {
super(primaryModel);
}
public void setTicks(long ticks) {
this.delay = ticks * TICK;
}
@Override
public void synchronize() {
// don't log anything until 'ticks' is set
if (this.delay == 0) {
super.synchronize();
} else {
log("SYNC thread start - sync duration will be: " + (this.delay / TICK) + " ticks");
try {
Thread.sleep(this.delay);
} catch (InterruptedException ex) {
log("SYNC thread interrupted");
TestTools.sleep(TICK);
return; // stop synchronize (i.e. don't call super.synchronize())
}
super.synchronize();
log("SYNC thread stop");
}
}
}
public void testDEBUG() {
assertFalse(DEBUG);
}
public static final boolean DEBUG = false;
public static void log(String message) {
if (DEBUG) {
log_(message);
}
}
private static void log_(String message) {
System.out.print(timestamp());
System.out.print(' ');
System.out.print(message);
System.out.println();
}
public static String timestamp() {
return String.valueOf((System.currentTimeMillis() % 10000) / 1000f);
}
}