/*
* Copyright 2012 LinkedIn, Inc
*
* 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 voldemort.utils.pool;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
public class QueuedKeyedResourcePoolContentionTest extends KeyedResourcePoolContentionTest {
protected QueuedKeyedResourcePool<String, TestResource> queuedPool;
@Before
@Override
public void setUp() {
super.setUp();
this.queuedPool = new QueuedKeyedResourcePool<String, TestResource>(factory, config);
super.pool = queuedPool;
TestResourceRequest.usedResourceCount.set(0);
TestResourceRequest.handledTimeoutCount.set(0);
TestResourceRequest.handledExceptionCount.set(0);
}
@Test
public void contendForQueue() throws Exception {
// Over ride some set up
super.config = new ResourcePoolConfig().setMaxPoolSize(POOL_SIZE)
.setTimeout(TIMEOUT_MS * 50, TimeUnit.MILLISECONDS);
this.queuedPool = new QueuedKeyedResourcePool<String, TestResource>(factory, config);
super.pool = queuedPool;
int numEnqueuers = POOL_SIZE * 2;
int numEnqueues = 10 * 1000;
String key = "Key";
float invalidationRate = (float) 0.25;
CountDownLatch waitForThreads = new CountDownLatch(numEnqueuers);
CountDownLatch waitForEnqueuers = new CountDownLatch(numEnqueuers);
for(int i = 0; i < numEnqueuers; ++i) {
new Thread(new Enqueuers(waitForThreads,
waitForEnqueuers,
key,
numEnqueues,
invalidationRate)).start();
}
try {
waitForEnqueuers.await();
assertEquals(POOL_SIZE, this.queuedPool.getTotalResourceCount());
assertEquals(POOL_SIZE, this.queuedPool.getCheckedInResourceCount());
assertEquals(0, this.queuedPool.getRegisteredResourceRequestCount());
assertEquals(numEnqueuers * numEnqueues, TestResourceRequest.usedResourceCount.get());
assertEquals(0, TestResourceRequest.handledTimeoutCount.get());
assertEquals(0, TestResourceRequest.handledExceptionCount.get());
} catch(InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void contendForQueueAndPool() throws Exception {
// Over ride some set up
super.config = new ResourcePoolConfig().setMaxPoolSize(POOL_SIZE)
.setTimeout(TIMEOUT_MS * 100, TimeUnit.MILLISECONDS);
this.queuedPool = new QueuedKeyedResourcePool<String, TestResource>(factory, config);
super.pool = queuedPool;
int numEnqueuers = POOL_SIZE;
int numCheckers = POOL_SIZE;
int numEnqueues = 10 * 1000;
String key = "Key";
float invalidationRate = (float) 0.25;
CountDownLatch waitForThreadsStart = new CountDownLatch(numEnqueuers + numCheckers);
CountDownLatch waitForThreadsEnd = new CountDownLatch(numEnqueuers + numCheckers);
for(int i = 0; i < numEnqueuers; ++i) {
new Thread(new Enqueuers(waitForThreadsStart,
waitForThreadsEnd,
key,
numEnqueues,
invalidationRate)).start();
}
for(int i = 0; i < numCheckers; ++i) {
new Thread(new Checkers(waitForThreadsStart,
waitForThreadsEnd,
key,
numEnqueues,
invalidationRate)).start();
}
try {
waitForThreadsEnd.await();
assertEquals(this.queuedPool.getCheckedInResourceCount(),
this.queuedPool.getTotalResourceCount());
assertEquals(0, this.queuedPool.getRegisteredResourceRequestCount());
assertEquals(numEnqueuers * numEnqueues, TestResourceRequest.usedResourceCount.get());
assertEquals(0, TestResourceRequest.handledTimeoutCount.get());
assertEquals(0, TestResourceRequest.handledExceptionCount.get());
} catch(InterruptedException e) {
e.printStackTrace();
}
}
public class Enqueuers implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final String key;
private final int enqueues;
private int used;
Queue<TestResource> resources;
private Random random;
private float invalidationRate;
Enqueuers(CountDownLatch startSignal,
CountDownLatch doneSignal,
String key,
int enqueues,
float invalidationRate) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.key = key;
this.enqueues = enqueues;
this.used = 0;
resources = new ConcurrentLinkedQueue<TestResource>();
this.random = new Random();
this.invalidationRate = invalidationRate;
}
private void processAtMostOneEnqueuedResource() throws Exception {
TestResource tr = resources.poll();
if(tr != null) {
this.used++;
assertTrue(tr.isValid());
// Invalidate some resources (except on last few check ins)
float f = random.nextFloat();
if(f < invalidationRate && this.used < this.enqueues - POOL_SIZE) {
tr.invalidate();
}
Thread.yield();
queuedPool.checkin(key, tr);
Thread.yield();
}
}
@Override
public void run() {
startSignal.countDown();
try {
startSignal.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
try {
for(int i = 0; i < enqueues; ++i) {
long deadlineNs = System.nanoTime()
+ TimeUnit.MILLISECONDS.toNanos(config.getTimeout(TimeUnit.NANOSECONDS));
queuedPool.registerResourceRequest(key, new TestResourceRequest(deadlineNs,
resources));
Thread.yield();
processAtMostOneEnqueuedResource();
}
while(this.used < enqueues) {
processAtMostOneEnqueuedResource();
Thread.yield();
}
} catch(Exception e) {
fail(e.toString());
}
doneSignal.countDown();
}
}
}