/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.olivergondza.dumpling.factory;
import static com.github.olivergondza.dumpling.Util.only;
import static com.github.olivergondza.dumpling.Util.pause;
import static com.github.olivergondza.dumpling.model.ProcessThread.nameIs;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
import com.github.olivergondza.dumpling.DisposeRule;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsEmptyCollection;
import org.junit.Rule;
import org.junit.Test;
import com.github.olivergondza.dumpling.Util;
import com.github.olivergondza.dumpling.model.ThreadLock;
import com.github.olivergondza.dumpling.model.ThreadStatus;
import com.github.olivergondza.dumpling.model.jvm.JvmRuntime;
import com.github.olivergondza.dumpling.model.jvm.JvmThread;
import com.github.olivergondza.dumpling.model.jvm.JvmThreadSet;
import org.junit.rules.Timeout;
public class JvmRuntimeFactoryTest {
// Abort after 10 seconds - gets stuck on strange architectures
public @Rule Timeout timeout = new Timeout(100000);
public @Rule DisposeRule clean = new DisposeRule();
@Test
public synchronized void newThreadShouldNotBeAPartOfReportedRuntime() {
Thread thread = clean.register(new Thread(getClass().getName() + " not run"));
assertNull("Not started thread should not be a part fo runtime", forThread(thread));
}
@Test
public synchronized void runnableThreadStatus() {
Thread thread = clean.register(new Thread(getClass().getName() + " running") {
long a;
@Override
public void run() {
while (true) {
a = a + hashCode();
}
}
});
thread.start();
assertStatusIs(ThreadStatus.RUNNABLE, thread);
assertStateIs(Thread.State.RUNNABLE, thread);
}
@Test
public void sleepingThreadStatus() {
Thread thread = clean.register(new Thread(getClass().getName() + " sleeping") {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
// Ignore
}
}
});
thread.start();
assertStatusIs(ThreadStatus.SLEEPING, thread);
assertStateIs(Thread.State.TIMED_WAITING, thread);
}
@Test
public void waitingThreadStatus() {
WaitingThreadStatus thread = new WaitingThreadStatus();
thread.start();
clean.register(thread);
assertStatusIs(ThreadStatus.IN_OBJECT_WAIT, thread);
assertStateIs(Thread.State.WAITING, thread);
assertVerbIs("waiting on", thread);
JvmThread pt = forThread(thread);
assertThat(pt.getAcquiredLocks(), Matchers.<ThreadLock>empty());
assertThat(pt.getWaitingOnLock().getClassName(), equalTo(getClass().getCanonicalName() + "$WaitingThreadStatus"));
assertThat(pt.getWaitingToLock(), nullValue());
}
private static final class WaitingThreadStatus extends Thread {
private WaitingThreadStatus() {
super("JvmRuntimeFactoryTest#waitingThreadStatus");
}
@Override
public synchronized void run() {
try {
wait();
} catch (InterruptedException ex) {
// Ignore
}
}
}
@Test
public void timedWaitingThreadStatus() {
TimedWaiting thread = new TimedWaiting();
thread.start();
clean.register(thread);
assertStatusIs(ThreadStatus.IN_OBJECT_WAIT_TIMED, thread);
assertStateIs(Thread.State.TIMED_WAITING, thread);
JvmThread pt = forThread(thread);
assertThat(pt.getAcquiredLocks(), Matchers.<ThreadLock>empty());
assertThat(pt.getWaitingOnLock().getClassName(), equalTo(getClass().getCanonicalName() + "$TimedWaiting"));
assertThat(pt.getWaitingToLock(), nullValue());
}
private static final class TimedWaiting extends Thread {
private TimedWaiting() {
super("JvmRuntimeFactoryTest#timedWaitingThreadStatus");
}
@Override
public synchronized void run() {
try {
wait(10000);
} catch (InterruptedException ex) {
// Ignore
}
}
}
@Test
public void parkedThreadStatus() {
Thread thread = new Thread(getClass().getName() + " parked") {
@Override
public void run() {
for (; ; ) {
LockSupport.park();
}
}
};
thread.start();
clean.register(thread);
assertStatusIs(ThreadStatus.PARKED, thread);
assertStateIs(Thread.State.WAITING, thread);
JvmThread pt = forThread(thread);
assertThat(pt.getAcquiredLocks(), Matchers.<ThreadLock>empty());
assertThat(pt.getWaitingOnLock(), nullValue());
assertThat(pt.getWaitingToLock(), nullValue());
}
@Test
public void parkedTimedThreadStatus() {
Thread thread = new Thread(getClass().getName() + " parked timed") {
@Override
public void run() {
for (; ; ) {
LockSupport.parkNanos(1000000000L);
}
}
};
thread.start();
clean.register(thread);
assertStatusIs(ThreadStatus.PARKED_TIMED, thread);
assertStateIs(Thread.State.TIMED_WAITING, thread);
JvmThread pt = forThread(thread);
assertThat(pt.getAcquiredLocks(), Matchers.<ThreadLock>empty());
assertThat(pt.getWaitingOnLock(), nullValue());
assertThat(pt.getWaitingToLock(), nullValue());
}
@Test
public void parkedThreadWithBlockerStatus() {
final Object blocker = new Object();
Thread thread = new Thread(getClass().getName() + " parked") {
@Override
public void run() {
for (; ; ) {
LockSupport.park(blocker);
}
}
};
thread.start();
clean.register(thread);
assertStatusIs(ThreadStatus.PARKED, thread);
assertStateIs(Thread.State.WAITING, thread);
JvmThread pt = forThread(thread);
assertThat(pt.getAcquiredLocks(), Matchers.<ThreadLock>empty());
assertThat(pt.getWaitingOnLock(), equalTo(ThreadLock.fromInstance(blocker)));
assertThat(pt.getWaitingToLock(), nullValue());
}
@Test
public void parkedTimedThreadWithBlockerStatus() {
final Object blocker = new Object();
Thread thread = new Thread(getClass().getName() + " parked timed") {
@Override
public void run() {
for (; ; ) {
LockSupport.parkNanos(blocker, 1000000000L);
}
}
};
thread.start();
clean.register(thread);
assertStatusIs(ThreadStatus.PARKED_TIMED, thread);
assertStateIs(Thread.State.TIMED_WAITING, thread);
JvmThread pt = forThread(thread);
assertThat(pt.getAcquiredLocks(), Matchers.<ThreadLock>empty());
assertThat(pt.getWaitingOnLock(), equalTo(ThreadLock.fromInstance(blocker)));
assertThat(pt.getWaitingToLock(), nullValue());
}
@Test
public synchronized void blockedOnMonitorThreadStatus() {
Thread thread = new Thread(getClass().getName() + " waiting on monitor") {
@Override
public void run() {
synchronized (JvmRuntimeFactoryTest.this) {
hashCode();
}
}
};
thread.start();
clean.register(thread);
assertStatusIs(ThreadStatus.BLOCKED, thread);
assertStateIs(Thread.State.BLOCKED, thread);
assertVerbIs("waiting to lock", thread);
}
@Test
public void creatingAndTerminatingThreadsShouldBeHandledGracefully() {
final ThreadGroup group = new ThreadGroup("creatingAndTerminatingThreadsShouldBeHandledGracefully");
class Thrd extends Thread {
private int countdown;
public Thrd(int countdown) {
super(group, "creatingAndTerminatingThreadsShouldBeHandledGracefully thread " + countdown);
this.countdown = countdown;
}
@Override
public void run() {
synchronized (JvmRuntimeFactoryTest.class) {
if (countdown >= 0) {
new Thrd(countdown - 1).start();
}
}
}
}
int originalCount = runtime().getThreads().size();
new Thrd(100).start();
new Thrd(100).start();
new Thrd(100).start();
new Thrd(100).start();
new Thrd(100).start();
new Thrd(100).start();
new Thrd(100).start();
new Thrd(100).start();
JvmRuntime runtime;
do {
runtime = runtime();
} while (runtime.getThreads().size() > originalCount);
group.interrupt();
}
@Test
public void testThreadAttributes() {
Thread expected = Thread.currentThread();
JvmThread actual = runtime().getThreads().where(nameIs(expected.getName())).onlyThread();
assertThat(expected.getName(), equalTo(actual.getName()));
assertThat(expected.getState(), equalTo(actual.getState()));
assertThat(expected.getPriority(), equalTo(actual.getPriority()));
assertThat(expected.getId(), equalTo(actual.getId()));
}
@Test
public void monitorOwnerInObjectWait() throws Exception {
final Object lock = new Object();
Thread thread = new Thread("monitorOwnerOnObjectWait") {
@Override
public void run() {
try {
synchronized (lock) {
lock.wait();
}
} catch (InterruptedException ex) {
}
}
};
thread.start();
clean.register(thread);
Thread.sleep(100); // Wait until sleeping
synchronized(lock) {
// Waiting thread is not supposed to own the thread
JvmRuntime runtime = runtime();
JvmThread waiting = runtime.getThreads().where(nameIs("monitorOwnerOnObjectWait")).onlyThread();
assertThat(waiting.getAcquiredLocks(), IsEmptyCollection.<ThreadLock>empty());
assertThat(waiting.getStatus(), equalTo(ThreadStatus.IN_OBJECT_WAIT));
// Current thread is
JvmThread current = runtime.getThreads().forCurrentThread();
final Set<ThreadLock> expected = new HashSet<ThreadLock>(Collections.singletonList(
ThreadLock.fromInstance(lock)
));
assertThat(current.getAcquiredLocks(), equalTo(expected));
}
}
@Test
public void ownableSynchronizers() throws Exception {
final Lock lock = new ReentrantLock();
lock.lockInterruptibly();
Thread thread = null;
try {
thread = new Thread("ownableSynchronizers") {
@Override
public void run() {
try {
lock.lockInterruptibly(); // Block here
} catch (InterruptedException ex) {
}
}
};
thread.start();
clean.register(thread);
Thread.sleep(100); // Wait until blocked
JvmRuntime runtime = runtime();
JvmThread owner = runtime.getThreads().forCurrentThread();
JvmThread blocked = runtime.getThreads().where(nameIs("ownableSynchronizers")).onlyThread();
assertThat(owner.getStatus(), equalTo(ThreadStatus.RUNNABLE));
assertThat(blocked.getStatus(), equalTo(ThreadStatus.PARKED));
assertThat(only(owner.getAcquiredLocks()), equalTo(blocked.getWaitingOnLock()));
assertThat(blocked.getBlockingThread(), equalTo(owner));
} finally {
thread.interrupt();
lock.unlock();
}
}
@Test
public void multipleMonitorsOnSameStackFrame() throws Exception {
final Lock lock = new ReentrantLock();
final Object obj = new Object();
final String str = new String();
new Thread("multipleMonitors") {
@Override
public void run() {
synchronized (lock) {
synchronized (obj) {
synchronized (str) {
synchronized (str) {
pause(1000);
}
}
}
}
}
}.start();
pause(100);
JvmThreadSet monitors = runtime().getThreads().where(nameIs("multipleMonitors"));
// All locks on single frame should be reported. Outermost lock should
// be at the bottom (first), innermost last.
assertThat(monitors.toString(), containsString(Util.formatTrace(
"- locked " + ThreadLock.fromInstance(str),
"- locked " + ThreadLock.fromInstance(str),
"- locked " + ThreadLock.fromInstance(obj),
"- locked " + ThreadLock.fromInstance(lock)
)));
assertThat(monitors.onlyThread().getAcquiredLocks().size(), equalTo(3));
}
@Test
public void extractThread() {
JvmThreadSet threads = new JvmRuntimeFactory().currentRuntime().getThreads();
Thread currentThread = Thread.currentThread();
assertThat(currentThread.getId(), equalTo(threads.forCurrentThread().getId()));
assertThat(currentThread.getId(), equalTo(threads.forThread(currentThread).getId()));
assertNull(threads.forThread(null));
assertNull(threads.forThread(new Thread()));
}
private void assertStatusIs(ThreadStatus expected, Thread thread) {
assertEquals("Reported state: " + thread.getState(), expected, statusOf(thread));
}
private void assertStateIs(Thread.State expected, Thread thread) {
assertEquals("Reported state: " + thread.getState(), expected, statusOf(thread).getState());
}
private void assertVerbIs(String verb, Thread thread) {
pause(100);
assertThat(forThread(thread).toString(), containsString("- " + verb));
}
private ThreadStatus statusOf(Thread thread) {
pause(100);
final JvmThread processThread = forThread(thread);
if (processThread == null) throw new AssertionError(
"No process thread in runtime for " + thread.getName()
);
return processThread.getStatus();
}
private JvmRuntime runtime() {
return new JvmRuntimeFactory().currentRuntime();
}
private JvmThread forThread(Thread candidate) {
return runtime().getThreads().forThread(candidate);
}
}