/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed 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.springframework.batch.repeat.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.batch.repeat.callback.NestedRepeatCallback;
import org.springframework.batch.repeat.exception.ExceptionHandler;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
public class TaskExecutorRepeatTemplateAsynchronousTests extends AbstractTradeBatchTests {
RepeatTemplate template = getRepeatTemplate();
int count = 0;
// @Override
public RepeatTemplate getRepeatTemplate() {
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
template.setTaskExecutor(new SimpleAsyncTaskExecutor());
// Set default completion above number of items in input file
template.setCompletionPolicy(new SimpleCompletionPolicy(8));
return template;
}
@Test
public void testEarlyCompletionWithException() throws Exception {
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
template.setCompletionPolicy(new SimpleCompletionPolicy(20));
taskExecutor.setConcurrencyLimit(2);
template.setTaskExecutor(taskExecutor);
try {
template.iterate(new RepeatCallback() {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
count++;
throw new IllegalStateException("foo!");
}
});
fail("Expected IllegalStateException");
}
catch (IllegalStateException e) {
assertEquals("foo!", e.getMessage());
}
assertTrue("Too few attempts: " + count, count >= 1);
assertTrue("Too many attempts: " + count, count <= 10);
}
@Test
public void testExceptionHandlerSwallowsException() throws Exception {
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
template.setCompletionPolicy(new SimpleCompletionPolicy(4));
taskExecutor.setConcurrencyLimit(2);
template.setTaskExecutor(taskExecutor);
template.setExceptionHandler(new ExceptionHandler() {
@Override
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
count++;
}
});
template.iterate(new RepeatCallback() {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
throw new IllegalStateException("foo!");
}
});
assertTrue("Too few attempts: " + count, count >= 1);
assertTrue("Too many attempts: " + count, count <= 10);
}
@Test
public void testNestedSession() throws Exception {
RepeatTemplate outer = getRepeatTemplate();
RepeatTemplate inner = new RepeatTemplate();
outer.iterate(new NestedRepeatCallback(inner, new RepeatCallback() {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
count++;
assertNotNull(context);
assertNotSame("Nested batch should have new session", context, context.getParent());
assertSame(context, RepeatSynchronizationManager.getContext());
return RepeatStatus.FINISHED;
}
}) {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
count++;
assertNotNull(context);
assertSame(context, RepeatSynchronizationManager.getContext());
return super.doInIteration(context);
}
});
assertTrue("Too few attempts: " + count, count >= 1);
assertTrue("Too many attempts: " + count, count <= 10);
}
/**
* Run a batch with a single template that itself has an async task
* executor. The result is a batch that runs in multiple threads (up to the
* throttle limit of the template).
*
* @throws Exception
*/
@Test
public void testMultiThreadAsynchronousExecution() throws Exception {
final String threadName = Thread.currentThread().getName();
final Set<String> threadNames = new HashSet<String>();
final RepeatCallback callback = new RepeatCallback() {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
assertNotSame(threadName, Thread.currentThread().getName());
threadNames.add(Thread.currentThread().getName());
Thread.sleep(100);
Trade item = provider.read();
if (item != null) {
processor.write(Collections.singletonList(item));
}
return RepeatStatus.continueIf(item != null);
}
};
template.iterate(callback);
// Shouldn't be necessary to wait:
// Thread.sleep(500);
assertEquals(NUMBER_OF_ITEMS, processor.count);
assertTrue(threadNames.size() > 1);
}
@Test
public void testThrottleLimit() throws Exception {
int throttleLimit = 600;
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
taskExecutor.setConcurrencyLimit(300);
template.setTaskExecutor(taskExecutor);
template.setThrottleLimit(throttleLimit);
final String threadName = Thread.currentThread().getName();
final Set<String> threadNames = new HashSet<String>();
final List<String> items = new ArrayList<String>();
final RepeatCallback callback = new RepeatCallback() {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
assertNotSame(threadName, Thread.currentThread().getName());
Trade item = provider.read();
threadNames.add(Thread.currentThread().getName() + " : " + item);
items.add("" + item);
if (item != null) {
processor.write(Collections.singletonList(item));
// Do some more I/O
for (int i = 0; i < 10; i++) {
TradeItemReader provider = new TradeItemReader(resource);
provider.open(new ExecutionContext());
while (provider.read() != null)
continue;
provider.close();
}
}
return RepeatStatus.continueIf(item != null);
}
};
template.iterate(callback);
// Shouldn't be necessary to wait:
// Thread.sleep(500);
assertEquals(NUMBER_OF_ITEMS, processor.count);
assertTrue(threadNames.size() > 1);
int frequency = Collections.frequency(items, "null");
// System.err.println("Frequency: "+frequency);
assertTrue(frequency <= throttleLimit);
}
/**
* Wrap an otherwise synchronous batch in a callback to an asynchronous
* template.
*
* @throws Exception
*/
@Test
public void testSingleThreadAsynchronousExecution() throws Exception {
TaskExecutorRepeatTemplate jobTemplate = new TaskExecutorRepeatTemplate();
final RepeatTemplate stepTemplate = new RepeatTemplate();
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
taskExecutor.setConcurrencyLimit(2);
jobTemplate.setTaskExecutor(taskExecutor);
final String threadName = Thread.currentThread().getName();
final Set<String> threadNames = new HashSet<String>();
final RepeatCallback stepCallback = new ItemReaderRepeatCallback<Trade>(provider, processor) {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
assertNotSame(threadName, Thread.currentThread().getName());
threadNames.add(Thread.currentThread().getName());
Thread.sleep(100);
TradeItemReader provider = new TradeItemReader(resource);
provider.open(new ExecutionContext());
while (provider.read() != null)
;
return super.doInIteration(context);
}
};
RepeatCallback jobCallback = new RepeatCallback() {
@Override
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
stepTemplate.iterate(stepCallback);
return RepeatStatus.FINISHED;
}
};
jobTemplate.iterate(jobCallback);
// Shouldn't be necessary to wait:
// Thread.sleep(500);
assertEquals(NUMBER_OF_ITEMS, processor.count);
// Because of the throttling and queueing internally to a TaskExecutor,
// more than one thread will be used - the number used is the
// concurrency limit in the task executor, plus 1.
// System.err.println(threadNames);
assertTrue(threadNames.size() >= 1);
}
}