/*
* Copyright © 2015 Cask Data, 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 co.cask.cdap.data2.dataset2.cache;
import co.cask.cdap.common.utils.Tasks;
import co.cask.cdap.data.dataset.SystemDatasetInstantiator;
import co.cask.cdap.data2.dataset2.DynamicDatasetCache;
import co.cask.cdap.data2.dataset2.MultiThreadDatasetCache;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class MultiThreadDatasetCacheTest extends DynamicDatasetCacheTest {
@Override
protected DynamicDatasetCache createCache(SystemDatasetInstantiator instantiator,
Map<String, String> arguments,
Map<String, Map<String, String>> staticDatasets) {
return new MultiThreadDatasetCache(instantiator, txClient, NAMESPACE_ID, arguments, null, staticDatasets);
}
@Test
public void testDatasetCache() throws Throwable {
for (int i = 0; i < 25; i++) {
testDatasetCacheOnce();
}
}
private void testDatasetCacheOnce() throws Throwable {
Map<String, TestDataset> thread1map = new HashMap<>();
Map<String, TestDataset> thread2map = new HashMap<>();
AtomicReference<Throwable> exceptionFromThread1 = new AtomicReference<>();
AtomicReference<Throwable> exceptionFromThread2 = new AtomicReference<>();
Thread thread1 = createThread(thread1map, exceptionFromThread1);
Thread thread2 = createThread(thread2map, exceptionFromThread2);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
// validate that both threads were successful
assertNoError(exceptionFromThread1);
assertNoError(exceptionFromThread2);
// validate that the threads successfully received non-null and different instances of the datasets
Assert.assertNotNull(thread1map.get("a"));
Assert.assertNotNull(thread1map.get("b"));
Assert.assertNotSame(thread1map.get("a"), thread2map.get("a"));
Assert.assertNotSame(thread1map.get("b"), thread2map.get("b"));
// we want to test that the per-thread entries get removed when the thread goes away. Unfortunately there
// is not reliable way to force GC to collect a weak reference (which is required to trigger removal from
// the cache. The following code works every time when I run it manually.
//noinspection UnusedAssignment
thread1 = thread2 = null;
//noinspection UnusedAssignment
thread1map = thread2map = null;
Tasks.waitFor(true, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
System.gc();
return ((MultiThreadDatasetCache) cache).getCacheKeys().isEmpty();
}
}, 5, TimeUnit.SECONDS, 100, TimeUnit.MILLISECONDS);
}
private Thread createThread(final Map<String, TestDataset> datasetMap, final AtomicReference<Throwable> ref) {
return new Thread() {
@Override
public void run() {
try {
testDatasetCache(datasetMap);
} catch (Throwable e) {
ref.set(e);
}
}
};
}
private void assertNoError(AtomicReference<Throwable> ref) throws Throwable {
Throwable t = ref.get();
if (t != null) {
throw t;
}
}
}