/**
* (c) Copyright 2013 WibiData, Inc.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* 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.kiji.mapreduce.kvstore;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.hadoop.conf.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.kiji.mapreduce.kvstore.framework.KeyValueStoreConfiguration;
import org.kiji.mapreduce.kvstore.lib.InMemoryMapKeyValueStore;
public class TestKeyValueStoreReaderFactory {
private final int mNumThreads = 30;
private CyclicBarrier mBarrier;
private Collection<ReaderFetcher> mReaderFetchers;
private List<Future<KeyValueStoreReader>> mKVStoreReaderFutures;
private KeyValueStoreReaderFactory mFactory;
@Before
public void setup() throws Exception {
mBarrier = new CyclicBarrier(mNumThreads);
mReaderFetchers = Lists.newArrayList();
Map<String, KeyValueStore<?, ?>> keyValueStores = Maps.newHashMap();
keyValueStores.put("myStore", createKVStore());
mFactory = KeyValueStoreReaderFactory.create(keyValueStores);
}
@After
public void tearDown() {
mFactory.close();
}
/**
* A test for thread safety on reads/opens in KeyValueStoreReaderFactory's openStore method.
* A single pass doesn't guarantee thread safety, but the test will likely fail within a few
* runs if thread safety is broken.
*/
@Test
public void testOpenStoreThreadSafety() throws Exception {
for (int i = 0; i < mNumThreads; i++) {
ReaderFetcher rf = new ReaderFetcher(mFactory);
mReaderFetchers.add(rf);
}
ExecutorService pool = Executors.newFixedThreadPool(mNumThreads);
mKVStoreReaderFutures = pool.invokeAll(mReaderFetchers);
KeyValueStoreReader curReader, prevReader = mKVStoreReaderFutures.get(0).get();
// All opened readers should be the same object
for (int i = 1; i < mKVStoreReaderFutures.size(); i++) {
curReader = mKVStoreReaderFutures.get(i).get();
assertNotNull(curReader);
assertEquals("Two different readers opened", prevReader, curReader);
prevReader = curReader;
}
}
// Copied from TestInMemoryMapKeyValueStore.
private KeyValueStore<String, Integer> createKVStore() throws Exception {
final Map<String, Integer> map = Maps.newHashMap();
map.put("one", 1);
final InMemoryMapKeyValueStore<String, Integer> kvStore =
InMemoryMapKeyValueStore.fromMap(map);
final Configuration conf = new Configuration(false);
final KeyValueStoreConfiguration kvConf = KeyValueStoreConfiguration.fromConf(conf);
kvStore.storeToConf(kvConf);
// Deserialize the store and read the value back.
final InMemoryMapKeyValueStore<String, Integer> outKvStore =
new InMemoryMapKeyValueStore<String, Integer>();
outKvStore.initFromConf(kvConf);
return outKvStore;
}
private class ReaderFetcher implements Callable<KeyValueStoreReader> {
private KeyValueStoreReaderFactory mReaderFetcherFactory;
ReaderFetcher(KeyValueStoreReaderFactory factory) {
this.mReaderFetcherFactory = factory;
}
public KeyValueStoreReader call() throws Exception {
// Waits for all threads to hit this point before having them all openStore() at once.
mBarrier.await();
return mReaderFetcherFactory.openStore("myStore");
}
}
}