/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.processors.schedule;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.util.lang.GridTuple;
import org.apache.ignite.internal.util.typedef.CI1;
import org.apache.ignite.lang.IgniteCallable;
import org.apache.ignite.lang.IgniteFuture;
import org.apache.ignite.lang.IgniteRunnable;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.resources.LoggerResource;
import org.apache.ignite.scheduler.SchedulerFuture;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* Test for task scheduler.
*/
@SuppressWarnings({"ProhibitedExceptionDeclared", "TooBroadScope"})
public class GridScheduleSelfTest extends GridCommonAbstractTest {
/** */
private static final int NODES_CNT = 2;
/** */
private static AtomicInteger execCntr = new AtomicInteger(0);
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
startGrids(NODES_CNT);
}
/** {@inheritDoc} */
@Override protected void afterTestsStopped() throws Exception {
stopAllGrids();
super.afterTestsStopped();
}
/** {@inheritDoc} */
@Override protected void beforeTest() throws Exception {
execCntr.set(0);
}
/**
* @throws Exception If failed.
*/
public void testRunLocal() throws Exception {
for (int i = 0; i < NODES_CNT; i++) {
IgniteFuture<?> fut = grid(i).scheduler().runLocal(new TestRunnable());
assert fut.get() == null;
assertEquals(1, execCntr.getAndSet(0));
}
}
/**
* @throws Exception If failed.
*/
public void testCallLocal() throws Exception {
for (int i = 0; i < NODES_CNT; i++) {
IgniteFuture<?> fut = grid(i).scheduler().callLocal(new TestCallable());
assertEquals(1, fut.get());
assertEquals(1, execCntr.getAndSet(0));
}
}
/**
* @throws Exception If failed.
*/
public void testScheduleRunnable() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
SchedulerFuture<?> fut = null;
long freq = 60; // 1 minute frequency.
long delay = 2; // 2 seconds delay.
try {
// Execute 2 times after 2 seconds delay every minute.
fut = grid(0).scheduler().scheduleLocal(
new Runnable() {
@Override public void run() {
latch.countDown();
info(">>> EXECUTING SCHEDULED RUNNABLE! <<<");
}
},
"{2, 2} * * * * *");
assert !fut.isDone();
assert !fut.isCancelled();
assert fut.last() == null;
final AtomicInteger notifyCnt = new AtomicInteger();
fut.listen(new CI1<IgniteFuture<?>>() {
@Override public void apply(IgniteFuture<?> e) {
notifyCnt.incrementAndGet();
}
});
long timeTillRun = freq + delay;
info("Going to wait for the first run: " + timeTillRun);
latch.await(timeTillRun, SECONDS);
assertEquals(0, latch.getCount());
assert !fut.isDone();
assert !fut.isCancelled();
assert fut.last() == null;
info("Going to wait for 2nd run: " + timeTillRun);
// Wait until scheduling will be finished.
Thread.sleep(timeTillRun * 1000);
assert fut.isDone();
assert notifyCnt.get() == 2;
assert !fut.isCancelled();
assert fut.last() == null;
}
finally {
assert fut != null;
fut.cancel();
}
}
/**
* @throws Exception If failed.
*/
public void testScheduleCallable() throws Exception {
SchedulerFuture<Integer> fut = null;
long freq = 60; // 1 minute frequency.
long delay = 2; // 2 seconds delay.
try {
fut = grid(0).scheduler().scheduleLocal(new Callable<Integer>() {
private int cnt;
@Override public Integer call() {
info(">>> EXECUTING SCHEDULED CALLABLE! <<<");
return ++cnt;
}
}, "{1, 2} * * * * *");
final AtomicInteger notifyCnt = new AtomicInteger();
fut.listen(new CI1<IgniteFuture<?>>() {
@Override public void apply(IgniteFuture<?> e) {
notifyCnt.incrementAndGet();
}
});
assert !fut.isDone();
assert !fut.isCancelled();
assert fut.last() == null;
long timeTillRun = freq + delay;
info("Going to wait for the 1st run: " + timeTillRun);
assertEquals((Integer)1, fut.get(timeTillRun, SECONDS));
assertEquals((Integer)1, fut.last());
assert !fut.isDone();
assert !fut.isCancelled();
info("Going to wait for the 2nd run: " + timeTillRun);
assertEquals((Integer)2, fut.get(timeTillRun, SECONDS));
assertEquals((Integer)2, fut.last());
assert fut.isDone();
assert !fut.isCancelled();
}
finally {
assert fut != null;
fut.cancel();
}
}
/**
* @throws Exception If failed.
*/
public void testRunnableCancel() throws Exception {
SchedulerFuture fut = null;
final GridTuple<Integer> tpl = new GridTuple<>(0);
try {
fut = grid(0).scheduler().scheduleLocal(new Runnable() {
@Override public void run() {
tpl.set(tpl.get() + 1);
}
}, "{1, *} * * * * *");
assertEquals(Integer.valueOf(0), tpl.get());
fut.cancel();
assert fut.isCancelled();
assert fut.isDone();
assertEquals(Integer.valueOf(0), tpl.get());
try {
fut.get();
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
try {
fut.get(500, SECONDS);
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
}
finally {
assert fut != null;
if (!fut.isCancelled())
fut.cancel();
}
}
/**
* @throws Exception If failed.
*/
public void testInvalidPatterns() throws Exception {
Runnable run = new Runnable() {
@Override public void run() {
// No-op.
}
};
try {
// Invalid delay.
grid(0).scheduler().scheduleLocal(run, "{sdf, *} * * * * *").get();
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
try {
// Invalid delay.
grid(0).scheduler().scheduleLocal(run, "{**, *} * * * * *").get();
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
try {
// Invalid number of executions.
grid(0).scheduler().scheduleLocal(run, "{1, ghd} * * * * *").get();
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
try {
// Number of executions in pattern must be greater than zero or equal to "*".
grid(0).scheduler().scheduleLocal(run, "{*, 0} * * * * *").get();
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
try {
// Invalid cron expression.
grid(0).scheduler().scheduleLocal(run, "{2, 6} * * * * * * * * * *").get();
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
try {
// Invalid both delay and number of calls.
grid(0).scheduler().scheduleLocal(run, "{-2, -6} * * * * *").get();
fail("IgniteException must have been thrown");
}
catch (IgniteException e) {
info("Caught expected exception: " + e);
}
}
/**
* @throws Exception If failed.
*/
public void testNoNextExecutionTime() throws Exception {
Callable<Integer> run = new Callable<Integer>() {
@Override public Integer call() {
return 1;
}
};
SchedulerFuture<Integer> future = grid(0).scheduler().scheduleLocal(run, "{55} 53 3/5 * * *");
try {
future.get();
fail("Accepted wrong cron expression");
}
catch (IgniteException e) {
assertTrue(e.getMessage().startsWith("Invalid cron expression in schedule pattern"));
}
assertTrue(future.isDone());
assertEquals(0, future.nextExecutionTime());
assertEquals(0, future.nextExecutionTimes(2, System.currentTimeMillis()).length);
}
/**
* Waits until method {@link org.apache.ignite.scheduler.SchedulerFuture#last()} returns not a null value. Tries to
* call specified number of attempts with 100ms interval between them.
*
* @param fut Schedule future to call method on.
* @param attempts Max number of attempts to try.
* @return {@code true} if wait is successful, {@code false} if attempts are exhausted.
* @throws Exception If failed.
*/
@SuppressWarnings("BusyWait")
private boolean waitForLastResult(SchedulerFuture<Integer> fut, int attempts) throws Exception {
assert fut != null;
assert attempts > 0;
boolean success = false;
for (int i = 0; i < attempts; i++) {
if (fut.last() != null) {
success = true;
break;
}
Thread.sleep(100);
}
return success;
}
/**
* Test runnable job.
*/
private static class TestRunnable implements IgniteRunnable {
/** */
@IgniteInstanceResource
private Ignite ignite;
/** */
@LoggerResource
private IgniteLogger log;
/** @{inheritDoc} */
@Override public void run() {
log.info("Runnable job executed on node: " + ignite.cluster().localNode().id());
assert ignite != null;
execCntr.incrementAndGet();
}
}
/**
* Test callable job.
*/
private static class TestCallable implements IgniteCallable<Integer> {
/** */
@IgniteInstanceResource
private Ignite ignite;
/** */
@LoggerResource
private IgniteLogger log;
/** {@inheritDoc} */
@Override public Integer call() {
log.info("Callable job executed on node: " + ignite.cluster().localNode().id());
assert ignite != null;
return execCntr.incrementAndGet();
}
}
}