/* * 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); } }