/*
* Copyright 2015 Terracotta, Inc., a Software AG company.
*
* 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.terracotta.offheapstore.paging;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.junit.Assume;
import org.junit.Test;
import org.terracotta.offheapstore.WriteLockedOffHeapClockCache;
import org.terracotta.offheapstore.storage.StorageEngine;
import org.terracotta.offheapstore.storage.portability.StringPortability;
import org.terracotta.offheapstore.util.OffHeapAndDiskStorageEngineDependentTest;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.terracotta.offheapstore.util.MemoryUnit.MEGABYTES;
/**
*
* @author Chris Dennis
*/
public class CrossedStealingDeadlockIT extends OffHeapAndDiskStorageEngineDependentTest {
private static final Method DEADLOCKED_THREADS_METHOD;
static {
Method method;
try {
method = ThreadMXBean.class.getMethod("findDeadlockedThreads");
} catch (Throwable t) {
method = null;
}
DEADLOCKED_THREADS_METHOD = method;
}
public CrossedStealingDeadlockIT(TestMode mode) {
super(mode);
}
@Test
public void testRecursiveTableShrink() throws InterruptedException {
Assume.assumeThat(DEADLOCKED_THREADS_METHOD, notNullValue());
PageSource source = createPageSource(4, MEGABYTES);
Collection<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 4; i++) {
threads.add(new Thread(new Loader(createSingleStripedCache(source), 5, TimeUnit.SECONDS)));
}
for (Thread t : threads) {
t.start();
}
new DeadlockDetector(threads).run();
for (Thread t : threads) {
t.join();
}
}
private Map<String, String> createSingleStripedCache(PageSource source) {
StorageEngine<String, String> storageEngine = create(source, StringPortability.INSTANCE, StringPortability.INSTANCE, true, true);
return new WriteLockedOffHeapClockCache<String, String>(source, true, storageEngine);
}
static class Loader implements Runnable {
private final Map<String, String> cache;
private final long duration;
Loader(Map<String, String> cache, long duration, TimeUnit unit) {
this.cache = cache;
this.duration = unit.toNanos(duration);
}
@Override
public void run() {
Random rndm = new Random();
long end = System.nanoTime() + duration;
do {
byte[] data = new byte[16];
rndm.nextBytes(data);
UUID uuid = UUID.nameUUIDFromBytes(data);
cache.put(uuid.toString(), uuid.toString());
} while (System.nanoTime() < end);
}
}
static class DeadlockDetector implements Runnable {
private final Collection<Thread> interest;
public DeadlockDetector(Collection<Thread> interest) {
this.interest = new ArrayList<Thread>(interest);
}
@SuppressWarnings("deprecation")
@Override
public void run() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
while (interestIsAlive()) {
long[] deadlocked;
try {
deadlocked = (long[]) DEADLOCKED_THREADS_METHOD.invoke(threadBean);
} catch (NoSuchMethodError e) {
Assume.assumeNoException(e);
throw e;
} catch (Throwable t) {
throw new AssertionError(t);
}
if (deadlocked == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
//ignore
}
} else {
ThreadInfo[] threads = threadBean.getThreadInfo(deadlocked, Integer.MAX_VALUE);
synchronized (System.err) {
System.err.println("Deadlocked Threads:");
for (ThreadInfo tInfo : threads) {
System.err.println(dumpThread(tInfo));
}
}
for (Thread t : interest) {
t.stop(new AssertionError("Deadlock was detected"));
}
throw new AssertionError("Deadlock was detected");
}
}
}
private boolean interestIsAlive() {
for (Thread t : interest) {
if (t.isAlive()) {
return true;
}
}
return false;
}
}
private static String dumpThread(ThreadInfo thread) {
StringBuilder sb = new StringBuilder("\"" + thread.getThreadName() + "\"" +
" Id=" + thread.getThreadId() + " " +
thread.getThreadState());
if (thread.getLockName() != null) {
sb.append(" on " + thread.getLockName());
}
if (thread.getLockOwnerName() != null) {
sb.append(" owned by \"" + thread.getLockOwnerName() +
"\" Id=" + thread.getLockOwnerId());
}
if (thread.isSuspended()) {
sb.append(" (suspended)");
}
if (thread.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
StackTraceElement[] trace = thread.getStackTrace();
for (int i = 0; i < trace.length; i++) {
StackTraceElement ste = trace[i];
sb.append("\tat " + ste.toString());
sb.append('\n');
}
sb.append('\n');
return sb.toString();
}
}