/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.test.impl.service.task;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ExceptionRetryStatus;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.PeriodicTaskHandle;
import com.sun.sgs.app.RunWithNewIdentity;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TransactionException;
import com.sun.sgs.app.TransactionNotActiveException;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.impl.auth.IdentityImpl;
import com.sun.sgs.impl.service.task.TaskServiceImpl;
import com.sun.sgs.impl.util.AbstractService.Version;
import com.sun.sgs.kernel.ComponentRegistry;
import com.sun.sgs.kernel.KernelRunnable;
import com.sun.sgs.kernel.TransactionScheduler;
import com.sun.sgs.service.DataService;
import com.sun.sgs.service.NodeMappingService;
import com.sun.sgs.service.TaskService;
import com.sun.sgs.service.TransactionProxy;
import com.sun.sgs.test.util.Constants;
import com.sun.sgs.test.util.DummyKernelRunnable;
import com.sun.sgs.test.util.SgsTestNode;
import com.sun.sgs.test.util.TestAbstractKernelRunnable;
import com.sun.sgs.tools.test.FilteredNameRunner;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Test the TaskServiceImpl class */
@RunWith(FilteredNameRunner.class)
public class TestTaskServiceImpl extends Assert {
// the pending namespace in the TaskService
// NOTE: this assumes certain private structure in the task service
private static final String PENDING_NS =
TaskServiceImpl.DS_PREFIX + "Pending.";
/** Version information from WatchdogServiceImpl class. */
private final String VERSION_KEY;
private final int MAJOR_VERSION;
private final int MINOR_VERSION;
/** The node that creates the servers */
private SgsTestNode serverNode;
public static TransactionProxy txnProxy;
private ComponentRegistry systemRegistry;
private Properties serviceProps;
/** The transaction scheduler. */
private TransactionScheduler txnScheduler;
/** The owner for tasks I initiate. */
private Identity taskOwner;
private DataService dataService;
private NodeMappingService mappingService;
private TaskService taskService;
/** The continue threshold from the TaskServiceImpl. */
private long continueThreshold;
private static Field getField(Class cl, String name) throws Exception {
Field field = cl.getDeclaredField(name);
field.setAccessible(true);
return field;
}
/**
* Test management.
*/
public TestTaskServiceImpl() throws Exception {
Class cl = TaskServiceImpl.class;
VERSION_KEY = (String) getField(cl, "VERSION_KEY").get(null);
MAJOR_VERSION = getField(cl, "MAJOR_VERSION").getInt(null);
MINOR_VERSION = getField(cl, "MINOR_VERSION").getInt(null);
}
@Before
public void setUp() throws Exception {
setUp(null, true);
}
protected void setUp(Properties props, boolean clean) throws Exception {
serverNode = new SgsTestNode("TestTaskServiceImpl", null, props, clean);
txnProxy = serverNode.getProxy();
systemRegistry = serverNode.getSystemRegistry();
serviceProps = serverNode.getServiceProperties();
txnScheduler = systemRegistry.getComponent(TransactionScheduler.class);
taskOwner = txnProxy.getCurrentOwner();
dataService = serverNode.getDataService();
mappingService = serverNode.getNodeMappingService();
taskService = serverNode.getTaskService();
continueThreshold = Long.valueOf(serviceProps.getProperty(
"com.sun.sgs.impl.service.task.continue.threshold"));
// add a counter for use in some of the tests, so we don't have to
// check later if it's present
if (clean) {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() throws Exception {
dataService.setBinding("counter", new Counter());
}
}, taskOwner);
}
}
@After
public void tearDown() throws Exception {
serverNode.shutdown(true);
}
/**
* Constructor tests.
*/
@Test
public void testConstructorNullArgs() throws Exception {
try {
new TaskServiceImpl(null, systemRegistry, txnProxy);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
try {
new TaskServiceImpl(new Properties(), null, txnProxy);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
try {
new TaskServiceImpl(new Properties(), systemRegistry, null);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
}
@Test
public void testConstructorNoScheduler() throws Exception {
Class<?> criClass =
Class.forName("com.sun.sgs.impl.kernel.ComponentRegistryImpl");
Constructor<?> criCtor = criClass.getDeclaredConstructor(new Class[] {});
criCtor.setAccessible(true);
try {
new TaskServiceImpl(serviceProps,
(ComponentRegistry)
criCtor.newInstance(new Object[] {}),
txnProxy);
fail("Expected MissingResourceException");
} catch (MissingResourceException e) {
System.err.println(e);
}
}
/** Version tests */
@Test
public void testConstructedVersion() throws Exception {
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() {
Version version = (Version)
serverNode.getDataService()
.getServiceBinding(VERSION_KEY);
if (version.getMajorVersion() != MAJOR_VERSION ||
version.getMinorVersion() != MINOR_VERSION)
{
fail("Expected service version (major=" +
MAJOR_VERSION + ", minor=" + MINOR_VERSION +
"), got:" + version);
}
}}, taskOwner);
}
@Test
public void testConstructorWithCurrentVersion() throws Exception {
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() {
Version version = new Version(MAJOR_VERSION, MINOR_VERSION);
serverNode.getDataService()
.setServiceBinding(VERSION_KEY, version);
}}, taskOwner);
new TaskServiceImpl(serviceProps, systemRegistry, txnProxy);
}
@Test
public void testConstructorWithMajorVersionMismatch() throws Exception {
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() {
Version version =
new Version(MAJOR_VERSION + 1, MINOR_VERSION);
serverNode.getDataService()
.setServiceBinding(VERSION_KEY, version);
}}, taskOwner);
try {
new TaskServiceImpl(serviceProps, systemRegistry, txnProxy);
fail("Expected IllegalStateException");
} catch (IllegalStateException e) {
System.err.println(e);
}
}
@Test
public void testConstructorWithMinorVersionMismatch() throws Exception {
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() {
Version version =
new Version(MAJOR_VERSION, MINOR_VERSION + 1);
serverNode.getDataService()
.setServiceBinding(VERSION_KEY, version);
}}, taskOwner);
try {
new TaskServiceImpl(serviceProps, systemRegistry, txnProxy);
fail("Expected IllegalStateException");
} catch (IllegalStateException e) {
System.err.println(e);
}
}
/**
* getName tests.
*/
@Test
public void testGetName() {
assertNotNull(taskService.getName());
}
/**
* TaskManager tests.
*/
@Test
public void testScheduleTaskNullArgs() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
try {
taskService.scheduleTask(null);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
try {
taskService.scheduleTask(null, 100L);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
try {
taskService.schedulePeriodicTask(null, 100L, 100L);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
}
}, taskOwner);
}
@Test
public void testScheduleTaskNotSerializable() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Task task = new NonSerializableTask();
try {
taskService.scheduleTask(task);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
try {
taskService.scheduleTask(task, 100L);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
try {
taskService.schedulePeriodicTask(task, 100L, 100L);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
}
}, taskOwner);
}
@Test
public void testScheduleTaskNotManagedObject() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Task task = new NonManagedTask(taskOwner);
try {
taskService.scheduleTask(task);
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
try {
taskService.scheduleTask(task, 100L);
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
try {
PeriodicTaskHandle handle =
taskService.schedulePeriodicTask(task, 100L, 100L);
handle.cancel();
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
}
}, taskOwner);
}
@Test
public void testScheduleTaskIsManagedObject() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Task task = new ManagedTask();
try {
taskService.scheduleTask(task);
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
try {
taskService.scheduleTask(task, 100L);
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
try {
PeriodicTaskHandle handle =
taskService.schedulePeriodicTask(task, 100L, 100L);
handle.cancel();
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
}
}, taskOwner);
}
@Test
public void testScheduleNegativeTime() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Task task = new ManagedTask();
try {
taskService.scheduleTask(task, -1L);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
try {
taskService.schedulePeriodicTask(task, -1L, 100L);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
try {
taskService.schedulePeriodicTask(task, 100L, -1L);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
}
}, taskOwner);
}
@Test
public void testScheduleTaskNoTransaction() {
Task task = new ManagedTask();
try {
taskService.scheduleTask(task);
fail("Expected TransactionNotActiveException");
} catch (TransactionNotActiveException e) {
System.err.println(e);
}
try {
taskService.scheduleTask(task, 100L);
fail("Expected TransactionNotActiveException");
} catch (TransactionNotActiveException e) {
System.err.println(e);
}
try {
taskService.schedulePeriodicTask(task, 100L, 100L);
fail("Expected TransactionNotActiveException");
} catch (TransactionNotActiveException e) {
System.err.println(e);
}
}
@Test
public void testRunImmediateTasks() throws Exception {
// test with application identity
runImmediateTest(taskOwner);
// test with un-mapped identity
Identity newOwner = new IdentityImpl("id");
runImmediateTest(newOwner);
// test with mapped identity
mappingService.assignNode(TestTaskServiceImpl.class,
newOwner);
runImmediateTest(newOwner);
}
private void runImmediateTest(final Identity owner) throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Counter counter = getClearedCounter();
for (int i = 0; i < 3; i++) {
taskService.scheduleTask(new NonManagedTask(owner));
counter.increment();
}
}
}, owner);
Thread.sleep(400);
assertCounterClearXAction("Some immediate tasks did not run");
}
@Test
public void testRunNonRetriedTasks() throws Exception {
// NOTE: this test assumes a certain structure in the TaskService.
final Field reusableField = getReusableField();
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
taskService.scheduleTask(new NonRetryNonManagedTask(false));
}
}, taskOwner);
Thread.sleep(200);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() throws Exception {
String name = dataService.nextServiceBoundName(PENDING_NS);
if ((name != null) && (name.startsWith(PENDING_NS))) {
Object o = dataService.getServiceBinding(name);
if (! reusableField.getBoolean(o))
fail("Non-retried task didn't get removed or " +
"set for re-use");
}
}
}, taskOwner);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
taskService.scheduleTask(new NonRetryNonManagedTask(true));
}
}, taskOwner);
Thread.sleep(200);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() throws Exception {
String name = dataService.nextServiceBoundName(PENDING_NS);
if ((name != null) && (name.startsWith(PENDING_NS))) {
Object o = dataService.getServiceBinding(name);
if (! reusableField.getBoolean(o))
fail("Non-retried task didn't get removed or " +
"set for re-use");
}
}
}, taskOwner);
}
@Test
public void testRunPendingTasks() throws Exception {
// test with application identity
runPendingTest(taskOwner);
// test with un-mapped identity
Identity newOwner = new IdentityImpl("id");
runPendingTest(newOwner);
// test with mapped identity
mappingService.assignNode(TestTaskServiceImpl.class, newOwner);
runPendingTest(newOwner);
}
private void runPendingTest(final Identity owner) throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
AppContext.getDataManager();
Counter counter = getClearedCounter();
for (long i = 0; i < 3; i++) {
taskService.scheduleTask(new NonManagedTask(owner),
i * 100L);
counter.increment();
}
}
}, owner);
Thread.sleep(500);
assertCounterClearXAction("Some pending tasks did not run");
}
@Test
public void testRunPeriodicTasks() throws Exception {
// test with application identity
runPeriodicTest(taskOwner);
// test with un-mapped identity
Identity newOwner = new IdentityImpl("id");
runPeriodicTest(newOwner);
// test with mapped identity
mappingService.assignNode(TestTaskServiceImpl.class, newOwner);
runPeriodicTest(newOwner);
}
private void runPeriodicTest(final Identity owner) throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Counter counter = getClearedCounter();
for (int i = 0; i < 3; i++) {
PeriodicTaskHandle handle =
taskService.schedulePeriodicTask(
new NonManagedTask(owner), 20L * i, 500L);
dataService.setBinding("runHandle." + i,
new ManagedHandle(handle));
counter.increment();
counter.increment();
}
}
}, owner);
Thread.sleep(750);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
String name = dataService.nextBoundName("runHandle.");
while ((name != null) && (name.startsWith("runHandle."))) {
ManagedHandle mHandle =
(ManagedHandle) dataService.getBinding(name);
mHandle.cancel();
dataService.removeObject(mHandle);
dataService.removeBinding(name);
name = dataService.nextBoundName(name);
}
assertCounterClear("Some periodic tasks did not run");
}
}, taskOwner);
}
@Test
public void testCancelPeriodicTasksBasic() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
getClearedCounter();
// test the basic cancel operation, within a transaction
PeriodicTaskHandle handle =
taskService.schedulePeriodicTask(
new ManagedTask(), 100L, 100L);
try {
handle.cancel();
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
// test the basic cancel operation, between transactions
handle = taskService.
schedulePeriodicTask(new NonManagedTask(taskOwner),
500L, 100L);
dataService.setBinding("TestTaskServiceImpl.handle",
new ManagedHandle(handle));
}
}, taskOwner);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
ManagedHandle mHandle = (ManagedHandle)
dataService.getBinding("TestTaskServiceImpl.handle");
try {
mHandle.cancel();
} catch (Exception e) {
fail("Did not expect Exception: " + e);
}
dataService.removeObject(mHandle);
dataService.removeBinding("TestTaskServiceImpl.handle");
}
}, taskOwner);
Thread.sleep(500);
assertCounterClearXAction("Basic cancel of periodic tasks failed");
}
@Test
public void testCancelPeriodicTasksTxnCommitted() throws Exception {
final CancelPeriodicTask task = new CancelPeriodicTask();
txnScheduler.runTask(task, taskOwner);
try {
task.handle.cancel();
fail("Expected TransactionNotActiveException");
} catch (TransactionNotActiveException e) {
System.err.println(e);
}
Thread.sleep(400);
assertCounterClearXAction("Cancel outside of transaction took effect");
// Now cancel the task for real, to quiet messages during shutdown
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
task.handle.cancel();
}
}, taskOwner);
}
private class CancelPeriodicTask extends TestAbstractKernelRunnable {
PeriodicTaskHandle handle;
public void run() {
Counter counter = getClearedCounter();
handle =
taskService.schedulePeriodicTask(new ManagedTask(), 200L, 500L);
counter.increment();
}
}
@Test
public void testCancelPeriodicTasksTxnAborted() throws Exception {
CancelPeriodTaskAbort task = new CancelPeriodTaskAbort();
try {
txnScheduler.runTask(task, taskOwner);
fail("Expected the TransactionException we threw from task");
} catch (TransactionException expected) {
// Do nothing
}
try {
task.handle.cancel();
fail("Expected TransactionNotActiveException");
} catch (TransactionNotActiveException e) {
System.err.println(e);
}
}
private class CancelPeriodTaskAbort extends TestAbstractKernelRunnable {
PeriodicTaskHandle handle;
public void run() throws Exception {
handle =
taskService.schedulePeriodicTask(new ManagedTask(), 200L, 500L);
throw new TransactionException("simulate a transaction abort");
}
}
@Test
public void testCancelPeriodicTasksTwice() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
// test the basic cancel operation, within a transaction
PeriodicTaskHandle handle =
taskService.schedulePeriodicTask(
new ManagedTask(), 100L, 100L);
handle.cancel();
try {
handle.cancel();
fail("Expected ObjectNotFoundException");
} catch (ObjectNotFoundException e) {
System.err.println(e);
}
// test the basic cancel operation, between transactions
handle = taskService.
schedulePeriodicTask(new NonManagedTask(taskOwner),
500L, 500L);
dataService.setBinding("TestTaskServiceImpl.handle",
new ManagedHandle(handle));
}
}, taskOwner);
final GetManagedHandleTask task = new GetManagedHandleTask();
txnScheduler.runTask(task, taskOwner);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
try {
task.mHandle.cancel();
fail("Expected ObjectNotFoundException");
} catch (ObjectNotFoundException e) {
System.err.println(e);
}
dataService.removeObject(task.mHandle);
dataService.removeBinding("TestTaskServiceImpl.handle");
}
}, taskOwner);
}
private class GetManagedHandleTask extends TestAbstractKernelRunnable {
ManagedHandle mHandle;
public void run() {
mHandle = (ManagedHandle) dataService.getBinding(
"TestTaskServiceImpl.handle");
mHandle.cancel();
}
}
@Test
public void testCancelPeriodicTasksTaskRemoved() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
getClearedCounter();
ManagedTask task = new ManagedTask();
dataService.setBinding("TestTaskServiceImpl.task", task);
PeriodicTaskHandle handle =
taskService.schedulePeriodicTask(task, 500L, 100L);
dataService.setBinding("TestTaskServiceImpl.handle",
new ManagedHandle(handle));
}
}, taskOwner);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
dataService.removeObject(
dataService.getBinding("TestTaskServiceImpl.task"));
}
}, taskOwner);
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
ManagedHandle mHandle =
(ManagedHandle) dataService.getBinding(
"TestTaskServiceImpl.handle");
try {
mHandle.cancel();
} catch (ObjectNotFoundException e) {
fail("Did not exxpect ObjectNotFoundException");
}
dataService.removeObject(mHandle);
dataService.removeBinding("TestTaskServiceImpl.handle");
dataService.removeBinding("TestTaskServiceImpl.task");
}
}, taskOwner);
Thread.sleep(800);
assertCounterClearXAction("cancel of periodic tasks failed");
}
@Test
public void testShouldContinueTrue() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
assertTrue(taskService.shouldContinue());
}
}, taskOwner);
}
@Test
public void testShouldContinueFalse() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() throws Exception {
Thread.sleep(continueThreshold +
Constants.MAX_CLOCK_GRANULARITY);
assertFalse(taskService.shouldContinue());
}
}, taskOwner);
}
@Test(expected = TransactionNotActiveException.class)
public void testShouldContinueNoTransaction() throws Exception {
taskService.shouldContinue();
}
/**
* TaskService tests.
*/
@Test
public void testScheduleNonDurableTaskNullArgs() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
try {
taskService.scheduleNonDurableTask(null, false);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
try {
taskService.scheduleNonDurableTask(null, 10, false);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
System.err.println(e);
}
}
}, taskOwner);
}
@Test
public void testScheduleNonDurableTaskNegativeTime() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
KernelRunnable r = new DummyKernelRunnable();
try {
taskService.scheduleNonDurableTask(r, -1L, false);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
}
}, taskOwner);
}
@Test
public void testScheduleNonDurableTaskNoTransaction() {
KernelRunnableImpl task = new KernelRunnableImpl(null);
try {
taskService.scheduleNonDurableTask(task, false);
fail("Expected TransactionNotActiveException");
} catch (TransactionNotActiveException e) {
System.err.println(e);
}
try {
taskService.scheduleNonDurableTask(task, 100L, false);
fail("Expected TransactionNotActiveException");
} catch (TransactionNotActiveException e) {
System.err.println(e);
}
}
@Test
public void testRunImmediateNonDurableTasks() throws Exception {
final CountDownLatch latch = new CountDownLatch(3);
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() throws Exception {
KernelRunnable r = new TestAbstractKernelRunnable() {
public void run() throws Exception {
if (! txnProxy.getCurrentOwner().
equals(taskOwner)) {
throw new RuntimeException("New identity");
}
latch.countDown();
}
};
for (int i = 0; i < 3; i++)
taskService.scheduleNonDurableTask(r, false);
}
}, taskOwner);
assertTrue(latch.await(500L, TimeUnit.MILLISECONDS));
}
@Test
public void testRunPendingNonDurableTasks() throws Exception {
final CountDownLatch latch = new CountDownLatch(3);
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() throws Exception {
KernelRunnable r = new TestAbstractKernelRunnable() {
public void run() throws Exception {
if (! txnProxy.getCurrentOwner().
equals(taskOwner)) {
throw new RuntimeException("New identity");
}
latch.countDown();
}
};
for (int i = 0; i < 3; i++)
taskService.scheduleNonDurableTask(r, i * 100L, false);
}
}, taskOwner);
assertTrue(latch.await(500L, TimeUnit.MILLISECONDS));
}
@Test
public void testRunNonDurableTransactionalTasks() throws Exception {
final CountDownLatch latch = new CountDownLatch(2);
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() throws Exception {
KernelRunnable r = new TestAbstractKernelRunnable() {
public void run() throws Exception {
if (! txnProxy.getCurrentOwner().
equals(taskOwner)) {
throw new RuntimeException("New identity");
}
// make sure that we're run in a transaction
serverNode.getProxy().getCurrentTransaction();
latch.countDown();
}
};
taskService.scheduleNonDurableTask(r, true);
taskService.scheduleNonDurableTask(r, 100, true);
}
}, taskOwner);
assertTrue(latch.await(500L, TimeUnit.MILLISECONDS));
}
@Test
public void testRunNonDurableNonTransactionalTasks() throws Exception {
final CountDownLatch latch = new CountDownLatch(2);
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() throws Exception {
KernelRunnable r = new TestAbstractKernelRunnable() {
public void run() throws Exception {
if (! txnProxy.getCurrentOwner().
equals(taskOwner)) {
throw new RuntimeException("New identity");
}
try {
serverNode.getProxy().
getCurrentTransaction();
} catch (TransactionNotActiveException tnae) {
// make sure we're not in a transaction
latch.countDown();
}
}
};
taskService.scheduleNonDurableTask(r, false);
taskService.scheduleNonDurableTask(r, 100, false);
}
}, taskOwner);
assertTrue(latch.await(500L, TimeUnit.MILLISECONDS));
}
@Test
public void testRecoveryCleanup() throws Exception {
final SgsTestNode node = new SgsTestNode(serverNode, null, null);
final SgsTestNode node2 = new SgsTestNode(serverNode, null, null);
final String name =
TaskServiceImpl.DS_PREFIX + "Handoff." + node.getNodeId();
// verify that the handoff binding exists
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() throws Exception {
try {
dataService.getServiceBinding(name);
} catch (NameNotBoundException nnbe) {
node.shutdown(false);
node2.shutdown(false);
throw nnbe;
}
}
}, taskOwner);
// shutdown the node and verify that the handoff removal happens
node.shutdown(false);
String interval = serverNode.getServiceProperties().
getProperty(
"com.sun.sgs.impl.service.watchdog.server.renew.interval",
"500");
Thread.sleep(4 * Long.valueOf(interval));
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
try {
dataService.getServiceBinding(name);
fail("Expected NameNotBoundException");
} catch (NameNotBoundException nnbe) {}
}
}, taskOwner);
node2.shutdown(false);
}
@Test
public void testRunImmediateWithNewIdentity() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Counter counter = getClearedCounter();
taskService.scheduleTask(new NewIdentityTask(taskOwner));
counter.increment();
}
}, taskOwner);
Thread.sleep(100L);
assertCounterClearXAction("Immediate task did not have new identity");
}
@Test
public void testRunDelayedWithNewIdentity() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Counter counter = getClearedCounter();
taskService.
scheduleTask(new NewIdentityTask(taskOwner), 50L);
counter.increment();
}
}, taskOwner);
Thread.sleep(200L);
assertCounterClearXAction("Delayed task did not have new identity");
}
@Test
public void testRunPeriodicWithNewIdentity() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Counter counter = getClearedCounter();
taskService.schedulePeriodicTask(
new NewIdentityTask(taskOwner), 0, 200L);
counter.increment();
counter.increment();
}
}, taskOwner);
Thread.sleep(300L);
assertCounterClearXAction("Immediate task did not have new identity");
}
@Test
public void testRunNonDurableWithNewIdentity() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() throws Exception {
Identity owner = txnProxy.getCurrentOwner();
KernelRunnable r =
new NewIdentityKernelRunnable(owner, latch);
taskService.scheduleNonDurableTask(r, true);
}
}, taskOwner);
assertTrue(latch.await(100L, TimeUnit.MILLISECONDS));
}
@Test
public void testRunDelayedNonDurableWithNewIdentity() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
txnScheduler.runTask(new TestAbstractKernelRunnable() {
public void run() throws Exception {
Identity owner = txnProxy.getCurrentOwner();
KernelRunnable r =
new NewIdentityKernelRunnable(owner, latch);
taskService.scheduleNonDurableTask(r, 50L, true);
}
}, taskOwner);
assertTrue(latch.await(100L, TimeUnit.MILLISECONDS));
}
@Test
public void testRunDelayedAcrossShutdown() throws Exception {
// schedule a delayed task to run
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
AppContext.getDataManager();
Counter counter = getClearedCounter();
taskService.scheduleTask(new NonManagedTask(taskOwner),
500L);
counter.increment();
}
}, taskOwner);
// shutdown the server immediately , retaining the data store
// sleep past the delayed task start time and start back up
serverNode.shutdown(false);
Thread.sleep(500);
setUp(null, false);
// verify the delayed task does not run immediately on startup
Thread.sleep(100);
assertCounterNotClearXAction(
"Delayed task incorrectly ran immediately after restart");
// verify that the delayed task does run after waiting
Thread.sleep(500);
assertCounterClearXAction(
"Delayed task did not run on time after restart");
}
@Test
public void testRunPeriodicAcrossShutdown() throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
Counter counter = getClearedCounter();
for (int i = 0; i < 3; i++) {
taskService.schedulePeriodicTask(
new NonManagedTask(taskOwner), 100L * i, 1000L);
counter.increment();
counter.increment();
}
}
}, taskOwner);
// shutdown the server, retaining the data store
// sleep past the periodic tasks start times and start back up
serverNode.shutdown(false);
Thread.sleep(1500);
setUp(null, false);
// verify that the periodic tasks have only run once and have not
// immediately run a second time on startup
Thread.sleep(300);
assertCounterValueXAction(3,
"Periodic tasks incorrectly ran immediately after restart");
// verify that the periodic tasks do run after waiting
Thread.sleep(1000);
assertCounterClearXAction(
"Some periodic tasks did not run on time after restart");
}
/**
* Utility routines.
*/
private Counter getClearedCounter() {
Counter counter = (Counter) dataService.getBinding("counter");
dataService.markForUpdate(counter);
counter.clear();
return counter;
}
private void assertCounterValue(int value, String message) {
Counter counter = (Counter) dataService.getBinding("counter");
if (counter.value() != value) {
System.err.println("Counter assert failed: expected " + value +
", actual " + counter.value());
fail(message);
}
}
private void assertCounterValueXAction(final int value,
final String message)
throws Exception {
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
assertCounterValue(value, message);
}
}, taskOwner);
}
private void assertCounterClear(String message) {
Counter counter = (Counter) dataService.getBinding("counter");
if (! counter.isZero()) {
System.err.println("Counter assert failed: " + counter);
fail(message);
}
}
private void assertCounterClearXAction(final String message)
throws Exception
{
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
assertCounterClear(message);
}
}, taskOwner);
}
private void assertCounterNotClear(String message) {
Counter counter = (Counter) dataService.getBinding("counter");
if (counter.isZero()) {
System.err.println("Counter assert failed: " + counter);
fail(message);
}
}
private void assertCounterNotClearXAction(final String message)
throws Exception
{
txnScheduler.runTask(
new TestAbstractKernelRunnable() {
public void run() {
assertCounterNotClear(message);
}
}, taskOwner);
}
private static Field getReusableField() throws Exception {
Class pendingTaskClass =
Class.forName("com.sun.sgs.impl.service.task.PendingTask");
Field reusableField = pendingTaskClass.getDeclaredField("reusable");
reusableField.setAccessible(true);
return reusableField;
}
/**
* Utility classes.
*/
public static class Counter implements ManagedObject, Serializable {
private static final long serialVersionUID = 1;
private int count = 0;
public void clear() { count = 0; }
public void increment() { count++; }
public void decrement() { count--; }
public boolean isZero() { return count == 0; }
public int value() { return count; }
public String toString() { return "Counter value = " + count; }
}
public static abstract class AbstractTask implements Task, Serializable {
public void run() throws Exception {
DataManager dataManager = AppContext.getDataManager();
Counter counter = (Counter) dataManager.getBinding("counter");
dataManager.markForUpdate(counter);
counter.decrement();
}
}
public static class ManagedTask extends AbstractTask
implements ManagedObject {
private static final long serialVersionUID = 1;
}
public static class NonManagedTask extends AbstractTask {
private static final long serialVersionUID = 1;
private final Identity expectedOwner;
public NonManagedTask(Identity assignedOwner) {
this.expectedOwner = assignedOwner;
}
public void run() throws Exception {
if (! txnProxy.getCurrentOwner().equals(expectedOwner)) {
throw new RuntimeException("Not running with same identity");
}
super.run();
}
}
public static class NonRetryNonManagedTask implements Task, Serializable {
private static final long serialVersionUID = 1;
private boolean throwRetryException;
public NonRetryNonManagedTask(boolean throwRetryException) {
this.throwRetryException = throwRetryException;
}
public void run() throws Exception {
if (throwRetryException)
throw new RetryException();
else
throw new Exception("This is a non-retry exception");
}
}
public static class RetryException extends Exception
implements ExceptionRetryStatus {
private static final long serialVersionUID = 1;
public RetryException() {
super("This is a retry exception with status false");
}
public boolean shouldRetry() {
return false;
}
}
public static class KernelRunnableImpl implements KernelRunnable {
private Counter counter;
public KernelRunnableImpl(Counter counter) {
this.counter = counter;
}
public String getBaseTaskType() {
return getClass().getName();
}
public void run() throws Exception {
synchronized (counter) {
counter.decrement();
}
}
}
public static class NonSerializableTask implements Task, ManagedObject {
public void run() throws Exception {}
}
public static class ManagedHandle implements ManagedObject, Serializable {
private static final long serialVersionUID = 1;
private final PeriodicTaskHandle handle;
public ManagedHandle(PeriodicTaskHandle handle) {
this.handle = handle;
}
public void cancel() {
AppContext.getDataManager().markForUpdate(this);
handle.cancel();
}
}
/** A utility class to test that new identities are correctly used. */
@RunWithNewIdentity
public static class NewIdentityTask extends AbstractTask
implements Serializable
{
private static final long serialVersionUID = 1;
private final Identity callingIdentity;
private Identity newIdentity = null;
public NewIdentityTask(Identity callingIdentity) {
this.callingIdentity = callingIdentity;
}
public void run() throws Exception {
// check that we were always run with a new identity
if (txnProxy.getCurrentOwner().equals(callingIdentity)) {
throw new RuntimeException("Not running with new identity");
}
// for periodic runs...
if (newIdentity == null) {
// if this is the first run, then store our new identity..
this.newIdentity = txnProxy.getCurrentOwner();
} else {
// ..otherwise check that we're using the same identity
if (! newIdentity.equals(txnProxy.getCurrentOwner())) {
throw new RuntimeException("Periodic task didn't " +
"keep using new identity");
}
}
// run the parent logic for counters
super.run();
}
}
@RunWithNewIdentity
public static class NewIdentityKernelRunnable implements KernelRunnable {
final Identity callingIdentity;
final CountDownLatch latch;
public NewIdentityKernelRunnable(Identity callingIdentity,
CountDownLatch latch)
{
this.callingIdentity = callingIdentity;
this.latch = latch;
}
public String getBaseTaskType() {
return "NewIdentityKernelRunnable";
}
public void run() throws Exception {
if (callingIdentity.equals(txnProxy.getCurrentOwner())) {
throw new RuntimeException("Not run with new identity");
}
latch.countDown();
}
}
}