/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.resource; import alluxio.Constants; import alluxio.clock.ManualClock; import alluxio.util.ThreadFactoryUtils; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public final class DynamicResourcePoolTest { /** * Resource class used to test {@link DynamicResourcePool}. */ private static final class Resource { private Integer mInteger = 0; // Threshold for invalid resource. private static final int INVALID_RESOURCE = 10; /** * Constructor of Resource class. * * @param i the number represents the current capacity of Resource */ public Resource(Integer i) { mInteger = i; } /** * Sets the the number representing current capacity of Resource and returns Resource Object. * * @param i the value of member variable represents the current capacity of Resource * @return the Resource object */ public Resource setInteger(Integer i) { mInteger = i; return this; } } /** * The subclass of DynamicResourcePool to be tested. */ private static final class TestPool extends DynamicResourcePool<Resource> { private int mGcThresholdInSecs = 120; private int mCounter = 0; private static final ScheduledExecutorService GC_EXECUTOR = new ScheduledThreadPoolExecutor(5, ThreadFactoryUtils.build("TestPool-%d", true)); /** * Constructor of TestPool class. * * @param options the Options object to set ScheduledExecutorService object * @param clock the object of Clock class */ public TestPool(Options options, ManualClock clock) { super(options.setGcExecutor(GC_EXECUTOR)); mClock = clock; } /** * Constructor of TestPool class. * * @param options the Options object to set ScheduledExecutorService object */ public TestPool(Options options) { super(options.setGcExecutor(GC_EXECUTOR)); } @Override protected boolean shouldGc(ResourceInternal<Resource> resourceInternal) { return mClock.millis() - resourceInternal.getLastAccessTimeMs() >= (long) mGcThresholdInSecs * (long) Constants.SECOND_MS; } @Override protected boolean isHealthy(Resource resource) { return resource.mInteger < Resource.INVALID_RESOURCE; } @Override protected void closeResource(Resource resource) { resource.setInteger(Resource.INVALID_RESOURCE); } @Override protected void closeResourceSync(Resource resource) { closeResource(resource); } @Override protected Resource createNewResource() { return new Resource(mCounter++); } /** * Set the value representing the max value of Interval, managing when should Gc. * * @param gcThresholdInSecs the value of Gc Threshold Interval */ public void setGcThresholdInSecs(int gcThresholdInSecs) { mGcThresholdInSecs = gcThresholdInSecs; } } /** * Tests the logic to acquire a resource when the pool is not full. */ @Test public void acquireWithCapacity() throws Exception { TestPool pool = new TestPool(DynamicResourcePool.Options.defaultOptions()); List<Resource> resourceList = new ArrayList<>(); for (int i = 0; i < 3; i++) { Resource resource = pool.acquire(); resourceList.add(resource); Assert.assertEquals(i, resource.mInteger.intValue()); } for (Resource resource : resourceList) { pool.release(resource); } Set<Integer> resources = new HashSet<>(); for (int i = 0; i < 3; i++) { Resource resource = pool.acquire(); resources.add(resource.mInteger); } // Make sure we are not creating new resources. for (int i = 0; i < 3; i++) { Assert.assertTrue(resources.contains(i)); } } /** * Acquire without capacity. */ @Test public void acquireWithoutCapacity() throws Exception { TestPool pool = new TestPool(DynamicResourcePool.Options.defaultOptions().setMaxCapacity(1)); List<Resource> resourceList = new ArrayList<>(); boolean timeout = false; try { Resource resource = pool.acquire(); resourceList.add(resource); Assert.assertEquals(0, resource.mInteger.intValue()); resource = pool.acquire(1, TimeUnit.SECONDS); resourceList.add(resource); } catch (TimeoutException e) { timeout = true; } Assert.assertEquals(1, resourceList.size()); Assert.assertTrue(timeout); } /** * Tests the logic that invalid resource won't be acquired. */ @Test public void UnhealthyResource() throws Exception { TestPool pool = new TestPool(DynamicResourcePool.Options.defaultOptions()); Resource resource = pool.acquire(); Assert.assertEquals(0, resource.mInteger.intValue()); resource.setInteger(Resource.INVALID_RESOURCE); pool.release(resource); resource = pool.acquire(); // The 0-th resource is not acquired because it is unhealthy. Assert.assertEquals(1, resource.mInteger.intValue()); } /** * Tests the logic that the recently used resource is preferred. */ @Test public void acquireRentlyUsed() throws Exception { ManualClock manualClock = new ManualClock(); TestPool pool = new TestPool(DynamicResourcePool.Options.defaultOptions(), manualClock); List<Resource> resourceList = new ArrayList<>(); resourceList.add(pool.acquire()); resourceList.add(pool.acquire()); resourceList.add(pool.acquire()); pool.release(resourceList.get(2)); pool.release(resourceList.get(0)); manualClock.addTimeMs(1500); pool.release(resourceList.get(1)); for (int i = 0; i < 10; i++) { Resource resource = pool.acquire(); Assert.assertEquals(1, resource.mInteger.intValue()); pool.release(resource); } } @Test public void gc() throws Exception { ManualClock manualClock = new ManualClock(); TestPool pool = new TestPool( DynamicResourcePool.Options.defaultOptions().setGcIntervalMs(10).setInitialDelayMs(1), manualClock); pool.setGcThresholdInSecs(1); List<Resource> resourceList = new ArrayList<>(); resourceList.add(pool.acquire()); resourceList.add(pool.acquire()); pool.release(resourceList.get(0)); manualClock.addTimeMs(1001); // Sleep 1 second to make sure the GC has run. Thread.sleep(1000); // Resource 0 is gc-ed. Assert.assertEquals(2, pool.acquire().mInteger.intValue()); } @Test public void multiClients() throws Exception { TestPool pool = new TestPool(DynamicResourcePool.Options.defaultOptions().setMaxCapacity(1)); final Resource resource1 = pool.acquire(); Assert.assertEquals(0, resource1.mInteger.intValue()); class ReleaseThread extends Thread { private TestPool mPool; private Resource mResource; ReleaseThread(TestPool pool, Resource resource) { mPool = pool; mResource = resource; } @Override public void run() { try { // Sleep sometime to test wait logic. Thread.sleep(1000); } catch (InterruptedException e) { return; } mPool.release(mResource); } } ReleaseThread releaseThread = new ReleaseThread(pool, resource1); releaseThread.start(); Resource resource2 = pool.acquire(2, TimeUnit.SECONDS); Assert.assertEquals(0, resource2.mInteger.intValue()); } }