/* * 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.impl.kernel; import com.sun.sgs.app.TaskRejectedException; import com.sun.sgs.auth.Identity; import com.sun.sgs.kernel.KernelRunnable; import com.sun.sgs.kernel.NodeType; import com.sun.sgs.kernel.TaskQueue; import com.sun.sgs.kernel.TransactionScheduler; import com.sun.sgs.kernel.schedule.ScheduledTask; import com.sun.sgs.kernel.schedule.SchedulerRetryAction; import com.sun.sgs.kernel.schedule.SchedulerRetryPolicy; import com.sun.sgs.service.Transaction; import com.sun.sgs.service.TransactionProxy; import com.sun.sgs.test.util.SgsTestNode; import com.sun.sgs.test.util.TestAbstractKernelRunnable; import com.sun.sgs.tools.test.FilteredNameRunner; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import java.lang.reflect.Field; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import org.junit.runner.RunWith; /** * Basic tests for the TransactionScheduler interface. Note that reservation * and scheduling methods are already tested from the tests specific to the * {@code ApplicationScheduler}s, so there are no duplicated tests here. */ @RunWith(FilteredNameRunner.class) public class TestTransactionSchedulerImpl { private SgsTestNode serverNode = null; private TransactionSchedulerImpl txnScheduler; private Identity taskOwner; public TestTransactionSchedulerImpl() { } /** Per-test initialization */ @Before public void startup() throws Exception { Properties properties = SgsTestNode.getDefaultProperties("TestTransactionSchedulerImpl", null, null); properties.setProperty(StandardProperties.NODE_TYPE, NodeType.coreServerNode.name()); serverNode = new SgsTestNode("TestTransactionSchedulerImpl", null, properties); txnScheduler = (TransactionSchedulerImpl) serverNode. getSystemRegistry().getComponent(TransactionScheduler.class); taskOwner = serverNode.getProxy().getCurrentOwner(); } /** Per-test shutdown */ @After public void shutdown() throws Exception { if (serverNode != null) serverNode.shutdown(true); } /** * Test runTask. */ @Test (expected=NullPointerException.class) public void runTaskNullTask() throws Exception { txnScheduler.runTask(null, taskOwner); } @Test (expected=NullPointerException.class) public void runTaskNullOwner() throws Exception { txnScheduler.runTask(new TestAbstractKernelRunnable() { public void run() {} }, null); } @Test public void runTaskSingleTask() throws Exception { RunCountTestRunner runner = new RunCountTestRunner(1); txnScheduler.runTask(runner, taskOwner); assertEquals(0, runner.getRunCount()); } @Test public void scheduleSingleTask() throws Exception { RunCountTestRunner runner = new RunCountTestRunner(1); txnScheduler.scheduleTask(runner, taskOwner); Thread.sleep(500L); assertEquals(0, runner.getRunCount()); } @Test public void runTaskMultipleTasks() throws Exception { RunCountTestRunner runner = new RunCountTestRunner(4); txnScheduler.runTask(runner, taskOwner); assertEquals(3, runner.getRunCount()); txnScheduler.runTask(runner, taskOwner); assertEquals(2, runner.getRunCount()); txnScheduler.runTask(runner, taskOwner); assertEquals(1, runner.getRunCount()); txnScheduler.runTask(runner, taskOwner); assertEquals(0, runner.getRunCount()); } @Test public void runTaskTransactional() throws Exception { final TransactionProxy proxy = serverNode.getProxy(); txnScheduler.runTask(new TestAbstractKernelRunnable() { public void run() throws Exception { proxy.getCurrentTransaction(); proxy.getCurrentOwner(); } }, taskOwner); } @Test public void runTransactionInTransaction() throws Exception { final TransactionProxy proxy = serverNode.getProxy(); KernelRunnable task = new TestAbstractKernelRunnable() { public void run() throws Exception { final Transaction t = proxy.getCurrentTransaction(); txnScheduler.runTask(new TestAbstractKernelRunnable() { public void run() throws Exception { Transaction t2 = proxy.getCurrentTransaction(); assertTrue(t.equals(t2)); } }, taskOwner); } }; txnScheduler.runTask(task, taskOwner); } @Test public void runTransactionFromScheduledTask() throws Exception { final RunCountTestRunner countRunner = new RunCountTestRunner(1); KernelRunnable task = new TestAbstractKernelRunnable() { public void run() throws Exception { txnScheduler.runTask(countRunner, taskOwner); } }; txnScheduler.scheduleTask(task, taskOwner); Thread.sleep(500L); assertEquals(0, countRunner.getRunCount()); } /** * Test runUnboundedTask */ @Test (expected=NullPointerException.class) public void runUnboundedTaskNullTask() throws Exception { txnScheduler.runUnboundedTask(null, taskOwner); } @Test (expected=NullPointerException.class) public void runUnboundedTaskNullOwner() throws Exception { txnScheduler.runUnboundedTask(new TestAbstractKernelRunnable() { public void run() { } }, null); } @Test public void runUnboundedTaskSingleTask() throws Exception { RunCountTestRunner runner = new RunCountTestRunner(1); txnScheduler.runUnboundedTask(runner, taskOwner); assertEquals(0, runner.getRunCount()); } @Test(timeout=2000) public void runUnboundedTaskLongTransaction() throws Exception { LongTransactionRunner runner = new LongTransactionRunner(1000L); txnScheduler.runUnboundedTask(runner, taskOwner); assertTrue(runner.isFinished()); } /** * Test interruption. */ @Test public void scheduleTransactionRetryAfterInterrupt() throws Exception { final AtomicInteger i = new AtomicInteger(0); final KernelRunnable r = new TestAbstractKernelRunnable() { public void run() throws Exception { if (i.getAndIncrement() == 0) throw new InterruptedException("test"); } }; txnScheduler.scheduleTask(r, taskOwner); Thread.sleep(200L); assertEquals(i.get(), 2); } @Test public void runTransactionInterrupted() throws Exception { final AtomicInteger i = new AtomicInteger(0); final KernelRunnable r = new TestAbstractKernelRunnable() { public void run() throws Exception { if (i.getAndIncrement() == 0) throw new InterruptedException("test"); } }; try { txnScheduler.runTask(r, taskOwner); fail("Expected Interrupted Exception"); } catch (InterruptedException ie) {} assertEquals(i.get(), 1); } /** * Test transaction handling. */ @Test public void runTaskWithRetry() throws Exception { RetryTestRunner runner = new RetryTestRunner(); txnScheduler.runTask(runner, taskOwner); assertTrue(runner.isFinished()); } @Test (expected=RuntimeException.class) public void runNonRetriedTask() throws Exception { txnScheduler.runTask(new TestAbstractKernelRunnable() { public void run() throws Exception { throw new RuntimeException("intentionally thrown"); } }, taskOwner); } @Test (expected=RuntimeException.class) public void runNonRetriedTaskExplicitAbort() throws Exception { final TransactionProxy proxy = serverNode.getProxy(); txnScheduler.runTask(new TestAbstractKernelRunnable() { public void run() throws Exception { RuntimeException re = new RuntimeException("intentional"); proxy.getCurrentTransaction().abort(re); throw re; } }, taskOwner); } @Test (expected=Error.class) public void testRunTransactionThrowsError() throws Exception { txnScheduler.runTask(new TestAbstractKernelRunnable() { public void run() throws Exception { throw new Error("intentionally thrown"); } }, taskOwner); } /** * Test createTaskQueue. */ @Test public void scheduleQueuedTasks() throws Exception { TaskQueue queue = txnScheduler.createTaskQueue(); AtomicInteger runCount = new AtomicInteger(0); for (int i = 0; i < 10; i++) queue.addTask(new DependentTask(runCount), taskOwner); Thread.sleep(500L); assertEquals(10, runCount.get()); } @Test (expected=NullPointerException.class) public void scheduleQueuedTasksNull() throws Exception { TaskQueue queue = txnScheduler.createTaskQueue(); queue.addTask(null, taskOwner); } @Test (expected=NullPointerException.class) public void scheduleQueuedTasksOwnerNull() throws Exception { TaskQueue queue = txnScheduler.createTaskQueue(); queue.addTask(new DependentTask(null), null); } /** * Test retry policy */ @Test public void dropFailedTask() throws Exception { final Exception result = new Exception("task failed"); replaceRetryPolicy(createRetryPolicy(SchedulerRetryAction.DROP)); final AtomicInteger i = new AtomicInteger(0); final KernelRunnable r = new TestAbstractKernelRunnable() { public void run() throws Exception { if (i.getAndIncrement() == 0) throw result; } }; try { txnScheduler.runTask(r, taskOwner); fail("expected Exception"); } catch(Exception e) { assertEquals(result, e); } finally { assertEquals(i.get(), 1); } } @Test public void retryFailedTask() throws Exception { final Exception result = new Exception("task failed"); replaceRetryPolicy(createRetryPolicy(SchedulerRetryAction.RETRY_NOW)); final AtomicInteger i = new AtomicInteger(0); final KernelRunnable r = new TestAbstractKernelRunnable() { public void run() throws Exception { if (i.getAndIncrement() == 0) throw result; } }; txnScheduler.runTask(r, taskOwner); assertEquals(i.get(), 2); } @Test public void handoffFailedTask() throws Exception { final Exception result = new Exception("task failed"); replaceRetryPolicy(createRetryPolicy(SchedulerRetryAction.RETRY_LATER)); final AtomicInteger i = new AtomicInteger(0); final KernelRunnable r = new TestAbstractKernelRunnable() { public void run() throws Exception { if (i.getAndIncrement() == 0) throw result; } }; txnScheduler.runTask(r, taskOwner); assertEquals(i.get(), 2); } /** * Utility methods. */ private void replaceRetryPolicy(SchedulerRetryPolicy policy) throws Exception { Field policyField = TransactionSchedulerImpl.class.getDeclaredField("retryPolicy"); policyField.setAccessible(true); policyField.set((TransactionSchedulerImpl) txnScheduler, policy); } private SchedulerRetryPolicy createRetryPolicy(final SchedulerRetryAction action) { return new SchedulerRetryPolicy() { public SchedulerRetryAction getRetryAction(ScheduledTask task) { return action; } }; } /** * Utility classes. */ private class LongTransactionRunner implements KernelRunnable { private boolean finished = false; private long sleep = 0; LongTransactionRunner(long sleep) { this.sleep = sleep; } public String getBaseTaskType() { return LongTransactionRunner.class.getName(); } public void run() throws Exception { Thread.sleep(sleep); finished = true; serverNode.getProxy().getCurrentTransaction(); } public boolean isFinished() { return finished; } } private class RunCountTestRunner implements KernelRunnable { private int runCount; RunCountTestRunner(int initialCount) { this.runCount = initialCount; } public String getBaseTaskType() { return RunCountTestRunner.class.getName(); } public void run() throws Exception { runCount--; } int getRunCount() { return runCount; } } private class RetryTestRunner implements KernelRunnable { private boolean hasRun = false; private boolean finished = false; public String getBaseTaskType() { return RetryTestRunner.class.getName(); } public void run() throws Exception { if (! hasRun) { hasRun = true; throw new TaskRejectedException("test"); } finished = true; } public boolean isFinished() { return finished; } } public static class DependentTask implements KernelRunnable { private static final Object lock = new Object(); private static boolean isRunning = false; private static int objNumberSequence = 0; private static volatile int nextExpectedObjNumber = 0; private final int objNumber; private final AtomicInteger runCounter; public DependentTask(AtomicInteger runCounter) { synchronized (lock) { objNumber = objNumberSequence++; } this.runCounter = runCounter; } public String getBaseTaskType() { return DependentTask.class.getName(); } public void run() throws Exception { synchronized (lock) { if (isRunning) throw new RuntimeException("another task was running"); isRunning = true; } if (nextExpectedObjNumber != objNumber) throw new RuntimeException("tasks ran out-of-order"); nextExpectedObjNumber++; runCounter.incrementAndGet(); isRunning = false; } } }