blob: 1828b90629f4a5f4de1143704692f81b4ee57527 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.solr.handler.admin;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.solr.SolrTestCaseJ4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.BeforeClass;
/**
* This test is currently flawed because it only ensures the 'test-*' threads don't exit before the asserts,
* it doesn't adequately ensure they 'start' before the asserts.
* Fixing the ownershipt should be possible using latches, but fixing the '*-blocked' threads may not be possible
* w/o polling
*/
public class ThreadDumpHandlerTest extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig.xml", "schema.xml");
}
public void testMonitor() throws Exception {
doTestMonitor(false);
}
/* checking for the BLOCKED thread requires some polling, so only do it nightly */
@Nightly
public void testMonitorBlocked() throws Exception {
doTestMonitor(true);
}
public void doTestMonitor(final boolean checkBlockedThreadViaPolling) throws Exception {
assumeTrue("monitor checking not supported on this JVM",
ManagementFactory.getThreadMXBean().isObjectMonitorUsageSupported());
/** unique class name to show up as a lock class name in output */
final class TestMonitorStruct { /* empty */ }
final List<String> failures = new ArrayList<>();
final CountDownLatch lockIsHeldLatch = new CountDownLatch(1);
final CountDownLatch doneWithTestLatch = new CountDownLatch(1);
final Object monitor = new TestMonitorStruct();
final Thread ownerT = new Thread(() -> {
synchronized (monitor) {
lockIsHeldLatch.countDown();
log.info("monitor ownerT waiting for doneWithTestLatch to release me...");
try {
if ( ! doneWithTestLatch.await(30, TimeUnit.SECONDS ) ){
failures.add("ownerT: never saw doneWithTestLatch released");
}
} catch (InterruptedException ie) {
failures.add("ownerT: " + ie.toString());
}
}
}, "test-thread-monitor-owner");
// only used if checkBlockedThreadViaPolling
// don't start until after lockIsHeldLatch fires
final Thread blockedT = new Thread(() -> {
log.info("blockedT waiting for monitor...");
synchronized (monitor) {
log.info("monitor now unblocked");
}
}, "test-thread-monitor-blocked");
try {
ownerT.start();
if ( ! lockIsHeldLatch.await(30, TimeUnit.SECONDS ) ){
failures.add("never saw lockIsHeldLatch released");
return;
}
assertQ(req("qt", "/admin/threads", "indent", "true")
// monitor owner 'ownerT'
// (which *MAY* also be waiting on doneWithTestLatch, but may not have reached that line yet)
, "//lst[@name='thread'][str[@name='name'][.='test-thread-monitor-owner']]"
+ " [arr[@name='monitors-locked']/str[contains(.,'TestMonitorStruct')]]"
);
if (checkBlockedThreadViaPolling) {
log.info("Also checking with blockedT thread setup via polling...");
try {
blockedT.setPriority(Thread.MAX_PRIORITY);
} catch (Exception e) {
log.warn("Couldn't set blockedT priority", e);
}
blockedT.start();
// there is no way to "await" on the situation of the 'blockedT' thread actually reaching the
// "synchronized" block and becoming BLOCKED ... we just have to Poll for it...
for (int i = 0; i < 500 && (! Thread.State.BLOCKED.equals(blockedT.getState())); i++) {
Thread.sleep(10); // 10ms at a time, at most 5 sec total
}
if (Thread.State.BLOCKED.equals(blockedT.getState())) {
assertQ(req("qt", "/admin/threads", "indent", "true")
// same monitor owner 'ownerT'
, "//lst[@name='thread'][str[@name='name'][.='test-thread-monitor-owner']]"
+ " [arr[@name='monitors-locked']/str[contains(.,'TestMonitorStruct')]]"
// blocked thread 'blockedT', waiting on the monitor
, "//lst[@name='thread'][str[@name='name'][.='test-thread-monitor-blocked']]"
+ " [str[@name='state'][.='BLOCKED']]"
+ " [lst[@name='lock-waiting'][lst[@name='owner']/str[.='test-thread-monitor-owner']]]"
);
}
}
} finally {
lockIsHeldLatch.countDown();
doneWithTestLatch.countDown();
ownerT.join(1000);
assertFalse("ownerT is still alive", ownerT.isAlive());
blockedT.join(1000);
assertFalse("blockedT is still alive", blockedT.isAlive());
}
}
public void testOwnableSync() throws Exception {
doTestOwnableSync(false);
}
/* checking for the WAITING thread requires some polling, so only do it nightly */
@Nightly
public void testOwnableSyncWaiting() throws Exception {
doTestOwnableSync(true);
}
public void doTestOwnableSync(final boolean checkWaitingThreadViaPolling) throws Exception {
assumeTrue("ownable sync checking not supported on this JVM",
ManagementFactory.getThreadMXBean().isSynchronizerUsageSupported());
/** unique class name to show up as a lock class name in output */
final class TestReentrantLockStruct extends ReentrantLock { /* empty */ }
final List<String> failures = new ArrayList<>();
final CountDownLatch lockIsHeldLatch = new CountDownLatch(1);
final CountDownLatch doneWithTestLatch = new CountDownLatch(1);
final ReentrantLock lock = new ReentrantLock();
final Thread ownerT = new Thread(() -> {
lock.lock();
try {
lockIsHeldLatch.countDown();
log.info("lock ownerT waiting for doneWithTestLatch to release me...");
try {
if ( ! doneWithTestLatch.await(5, TimeUnit.SECONDS ) ){
failures.add("ownerT: never saw doneWithTestLatch release");
}
} catch (InterruptedException ie) {
failures.add("ownerT: " + ie.toString());
}
} finally {
lock.unlock();
}
}, "test-thread-sync-lock-owner");
// only used if checkWaitingThreadViaPolling
// don't start until after lockIsHeldLatch fires
final Thread blockedT = new Thread(() -> {
log.info("blockedT waiting for lock...");
lock.lock();
try {
log.info("lock now unblocked");
} finally {
lock.unlock();
}
}, "test-thread-sync-lock-blocked");
try {
ownerT.start();
if ( ! lockIsHeldLatch.await(30, TimeUnit.SECONDS ) ){
failures.add("never saw lockIsHeldLatch released");
return;
}
assertQ(req("qt", "/admin/threads", "indent", "true")
// lock owner 'ownerT'
// (which *MAY* also be waiting on doneWithTestLatch, but may not have reached that line yet)
, "//lst[@name='thread'][str[@name='name'][.='test-thread-sync-lock-owner']]"
+ " [arr[@name='synchronizers-locked']/str[contains(.,'ReentrantLock')]]"
);
if (checkWaitingThreadViaPolling) {
log.info("Also checking with blockedT thread setup via polling...");
try {
blockedT.setPriority(Thread.MAX_PRIORITY);
} catch (Exception e) {
log.warn("Couldn't set blockedT priority", e);
}
blockedT.start();
// there is no way to "await" on the situation of the 'blockedT' thread actually reaches the lock()
// call and WAITING in the queue ... we just have to Poll for it...
for (int i = 0; i < 500 && (! lock.hasQueuedThread(blockedT)); i++) {
Thread.sleep(10); // 10ms at a time, at most 5 sec total
}
if (lock.hasQueuedThread(blockedT)) {
assertQ(req("qt", "/admin/threads", "indent", "true")
// same lock owner 'ownerT'
, "//lst[@name='thread'][str[@name='name'][.='test-thread-sync-lock-owner']]"
+ " [arr[@name='synchronizers-locked']/str[contains(.,'ReentrantLock')]]"
// blocked thread 'blockedT', waiting on the lock
, "//lst[@name='thread'][str[@name='name'][.='test-thread-sync-lock-blocked']]"
+ " [str[@name='state'][.='WAITING']]"
+ " [lst[@name='lock-waiting'][lst[@name='owner']/str[.='test-thread-sync-lock-owner']]]"
);
}
}
} finally {
lockIsHeldLatch.countDown();
doneWithTestLatch.countDown();
ownerT.join(1000);
assertFalse("ownerT is still alive", ownerT.isAlive());
blockedT.join(1000);
assertFalse("blockedT is still alive", blockedT.isAlive());
}
}
}