/**
* Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
*
* 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 com.linkedin.pinot.core.realtime.impl.dictionary;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Tests for concurrent read and write against REALTIME dictionary.
* <p>For now just test against {@link IntOnHeapMutableDictionary}. Index contiguous integers from 1 so that the index
* for each value is deterministic.
*/
public class ConcurrentReadWriteDictionaryTest {
private static final int NUM_ENTRIES = 1_000_000;
private static final int NUM_READERS = 5;
private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(NUM_READERS + 1);
private static final Random RANDOM = new Random();
private IntOnHeapMutableDictionary _intOnHeapMutableDictionary;
@BeforeMethod
public void setUp() {
_intOnHeapMutableDictionary = new IntOnHeapMutableDictionary();
}
@Test
public void testSingleReaderSingleWriter()
throws Exception {
Future<Void> readerFuture = EXECUTOR_SERVICE.submit(new Reader());
Future<Void> writerFuture = EXECUTOR_SERVICE.submit(new Writer());
readerFuture.get();
writerFuture.get();
}
@Test
public void testMultiReadersSingleWriter()
throws Exception {
Future[] readerFutures = new Future[NUM_READERS];
for (int i = 0; i < NUM_READERS; i++) {
readerFutures[i] = EXECUTOR_SERVICE.submit(new Reader());
}
Future<Void> writerFuture = EXECUTOR_SERVICE.submit(new Writer());
for (int i = 0; i < NUM_READERS; i++) {
readerFutures[i].get();
}
writerFuture.get();
}
@AfterClass
public void tearDown() {
EXECUTOR_SERVICE.shutdown();
}
/**
* Reader to read the index of each value after it's indexed into the dictionary, then get the value from the index.
* <p>We can assume that we always first get the index of a value, then use the index to fetch the value.
*/
private class Reader implements Callable<Void> {
@Override
public Void call()
throws Exception {
for (int i = 0; i < NUM_ENTRIES; i++) {
int dictId;
do {
dictId = _intOnHeapMutableDictionary.indexOf(i + 1);
} while (dictId < 0);
Assert.assertEquals(dictId, i);
Assert.assertEquals(_intOnHeapMutableDictionary.getIntValue(dictId), i + 1);
// Fetch value by a random existing dictId
int randomDictId = RANDOM.nextInt(i + 1);
Assert.assertEquals(_intOnHeapMutableDictionary.getIntValue(randomDictId), randomDictId + 1);
}
return null;
}
}
/**
* Writer to index value into dictionary, then check the index of the value.
*/
private class Writer implements Callable<Void> {
@Override
public Void call()
throws Exception {
for (int i = 0; i < NUM_ENTRIES; i++) {
_intOnHeapMutableDictionary.index(i + 1);
Assert.assertEquals(_intOnHeapMutableDictionary.indexOf(i + 1), i);
// Index a random existing value
int randomValue = RANDOM.nextInt(i + 1) + 1;
_intOnHeapMutableDictionary.index(randomValue);
Assert.assertEquals(_intOnHeapMutableDictionary.indexOf(randomValue), randomValue - 1);
}
return null;
}
}
}