// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.cache; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.net.URL; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.junit.Rule; import org.junit.Test; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.testutils.JOSMTestRules; import org.openstreetmap.josm.tools.Utils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Simple tests for ThreadPoolExecutor / HostLimitQueue veryfing, that this pair works OK * @author Wiktor Niesiobedzki */ public class HostLimitQueueTest { /** * Setup test. */ @Rule @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") public JOSMTestRules test = new JOSMTestRules().preferences().timeout(20 * 1000); private static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers, int queueLimit) { HostLimitQueue workQueue = new HostLimitQueue(queueLimit); ThreadPoolExecutor executor = new ThreadPoolExecutor( 0, // 0 so for unused thread pools threads will eventually die, freeing also the threadpool workers, // do not this number of threads 300, // keepalive for thread TimeUnit.SECONDS, workQueue, Utils.newThreadFactory(nameFormat, Thread.NORM_PRIORITY) ); workQueue.setExecutor(executor); return executor; } /** * Mock class for tests */ static class Task extends JCSCachedTileLoaderJob<String, CacheEntry> { private URL url; private AtomicInteger counter; Task(ICacheAccess<String, CacheEntry> cache, URL url, AtomicInteger counter) { super(cache, 1, 1, null); this.url = url; this.counter = counter; } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { Main.trace(e); } finally { this.counter.incrementAndGet(); executionFinished(); } } @Override public String getCacheKey() { return ""; } @Override public URL getUrl() throws IOException { return this.url; } @Override protected CacheEntry createCacheEntry(byte[] content) { return null; } } /** * Check if single threaded execution works properly * @throws Exception in case of error */ @Test public void testSingleThreadPerHost() throws Exception { ThreadPoolExecutor tpe = getNewThreadPoolExecutor("test-%d", 3, 1); ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("test", 3, 0, ""); AtomicInteger counter = new AtomicInteger(0); long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { tpe.execute(new Task(cache, new URL("http://localhost/"+i), counter)); } tpe.shutdown(); tpe.awaitTermination(15, TimeUnit.SECONDS); // at most it should take ~10 seconds, so after 15 it's already failed long duration = System.currentTimeMillis() - start; // check that all tasks were executed assertEquals(10, counter.get()); // although there are 3 threads, we can make only 1 parallel call to localhost // so it should take ~10 seconds to finish // if it's shorter, it means that host limit does not work assertTrue("Expected duration between 9 and 11 seconds not met. Actual duration: " + (duration /1000), duration < 11*1000 & duration > 9*1000); } /** * Check if two threaded execution work properly * @throws Exception in case of error */ @Test public void testMultipleThreadPerHost() throws Exception { ThreadPoolExecutor tpe = getNewThreadPoolExecutor("test-%d", 3, 2); ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("test", 3, 0, ""); AtomicInteger counter = new AtomicInteger(0); long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { tpe.execute(new Task(cache, new URL("http://hostlocal/"+i), counter)); } tpe.shutdown(); tpe.awaitTermination(15, TimeUnit.SECONDS); long duration = System.currentTimeMillis() - start; // check that all tasks were executed assertEquals(10, counter.get()); // although there are 3 threads, we can make only 2 parallel call to localhost // so it should take ~5 seconds to finish // if it's shorter, it means that host limit does not work assertTrue("Expected duration between 4 and 6 seconds not met. Actual duration: " + (duration /1000), duration < 6*1000 & duration > 4*1000); } /** * Check two hosts * @throws Exception in case of error */ @Test public void testTwoHosts() throws Exception { ThreadPoolExecutor tpe = getNewThreadPoolExecutor("test-%d", 3, 1); ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("test", 3, 0, ""); AtomicInteger counter = new AtomicInteger(0); long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { String url = (i % 2 == 0) ? "http://localhost" : "http://hostlocal"; tpe.execute(new Task(cache, new URL(url+i), counter)); } tpe.shutdown(); tpe.awaitTermination(15, TimeUnit.SECONDS); long duration = System.currentTimeMillis() - start; // check that all tasks were executed assertEquals(10, counter.get()); // although there are 3 threads, we can make only 1 parallel per host, and we have 2 hosts // so it should take ~5 seconds to finish // if it's shorter, it means that host limit does not work assertTrue("Expected duration between 4 and 6 seconds not met. Actual duration: " + (duration /1000), duration < 6*1000 & duration > 4*1000); } }