/* * 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.geode.management.internal.cli.commands; import static org.apache.geode.distributed.ConfigurationProperties.*; import org.apache.geode.cache.Cache; import org.apache.geode.cache.execute.Function; import org.apache.geode.cache.execute.FunctionContext; import org.apache.geode.cache.execute.FunctionService; import org.apache.geode.cache.execute.ResultCollector; import org.apache.geode.distributed.DistributedLockService; import org.apache.geode.distributed.internal.deadlock.GemFireDeadlockDetector; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; import org.apache.geode.management.cli.Result; import org.apache.geode.management.cli.Result.Status; import org.apache.geode.management.internal.cli.CliUtil; import org.apache.geode.management.internal.cli.i18n.CliStrings; import org.apache.geode.management.internal.cli.remote.CommandProcessor; import org.apache.geode.management.internal.cli.util.CommandStringBuilder; import org.apache.geode.test.dunit.Host; import org.apache.geode.test.dunit.SerializableCallable; import org.apache.geode.test.dunit.SerializableRunnable; import org.apache.geode.test.dunit.VM; import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase; import org.apache.geode.test.junit.categories.DistributedTest; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.File; import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT; import static org.apache.geode.test.dunit.Assert.*; import static org.apache.geode.test.dunit.Invoke.invokeInEveryVM; import static org.apache.geode.test.dunit.LogWriterUtils.getLogWriter; /** * This DUnit tests uses same code as GemFireDeadlockDetectorDUnitTest and uses the command * processor for executing the "show deadlock" command */ @Category(DistributedTest.class) public class ShowDeadlockDUnitTest extends JUnit4CacheTestCase { private static final long serialVersionUID = 1L; private static final Set<Thread> stuckThreads = Collections.synchronizedSet(new HashSet<Thread>()); private static final Map<String, String> EMPTY_ENV = Collections.emptyMap(); @Override public final void postSetUp() throws Exception { // This test does not require an actual Gfsh connection to work, however when run as part of a // suite, prior tests // may mess up the environment causing this test to fail. Setting this prevents false failures. CliUtil.isGfshVM = false; } @Override public final void preTearDownCacheTestCase() throws Exception { invokeInEveryVM(new SerializableRunnable() { private static final long serialVersionUID = 1L; public void run() { for (Thread thread : stuckThreads) { thread.interrupt(); } } }); CliUtil.isGfshVM = true; } @Test public void testNoDeadlock() throws ClassNotFoundException, IOException { Host host = Host.getHost(0); VM vm0 = host.getVM(0); VM vm1 = host.getVM(1); // Make sure a deadlock from a previous test is cleared. disconnectAllFromDS(); createCache(vm0); createCache(vm1); createCache(new Properties()); String fileName = "dependency.txt"; GemFireDeadlockDetector detect = new GemFireDeadlockDetector(); assertEquals(null, detect.find().findCycle()); CommandProcessor commandProcessor = new CommandProcessor(); CommandStringBuilder csb = new CommandStringBuilder(CliStrings.SHOW_DEADLOCK); csb.addOption(CliStrings.SHOW_DEADLOCK__DEPENDENCIES__FILE, fileName); Result result = commandProcessor.createCommandStatement(csb.toString(), EMPTY_ENV).process(); String deadLockOutputFromCommand = getResultAsString(result); getLogWriter().info("output = " + deadLockOutputFromCommand); assertEquals(true, result.hasIncomingFiles()); assertEquals(true, result.getStatus().equals(Status.OK)); assertEquals(true, deadLockOutputFromCommand.startsWith(CliStrings.SHOW_DEADLOCK__NO__DEADLOCK)); result.saveIncomingFiles(null); File file = new File(fileName); assertTrue(file.exists()); file.delete(); disconnectAllFromDS(); } private static final Lock lock = new ReentrantLock(); @Test public void testDistributedDeadlockWithFunction() throws InterruptedException, ClassNotFoundException, IOException { Host host = Host.getHost(0); VM vm0 = host.getVM(0); VM vm1 = host.getVM(1); String filename = "gfeDependency.txt"; InternalDistributedMember member1 = createCache(vm0); final InternalDistributedMember member2 = createCache(vm1); createCache(new Properties()); // Have two threads lock locks on different members in different orders. // This thread locks the lock member1 first, then member2. lockTheLocks(vm0, member2); // This thread locks the lock member2 first, then member1. lockTheLocks(vm1, member1); Thread.sleep(5000); CommandProcessor commandProcessor = new CommandProcessor(); CommandStringBuilder csb = new CommandStringBuilder(CliStrings.SHOW_DEADLOCK); csb.addOption(CliStrings.SHOW_DEADLOCK__DEPENDENCIES__FILE, filename); Result result = commandProcessor.createCommandStatement(csb.toString(), EMPTY_ENV).process(); String deadLockOutputFromCommand = getResultAsString(result); getLogWriter().info("Deadlock = " + deadLockOutputFromCommand); result.saveIncomingFiles(null); assertEquals(true, deadLockOutputFromCommand.startsWith(CliStrings.SHOW_DEADLOCK__DEADLOCK__DETECTED)); assertEquals(true, result.getStatus().equals(Status.OK)); File file = new File(filename); assertTrue(file.exists()); file.delete(); } private void createCache(Properties props) { getSystem(props); final Cache cache = getCache(); } private Properties createProperties(Host host, int locatorPort) { Properties props = new Properties(); props.setProperty(MCAST_PORT, "0"); // props.setProperty(DistributionConfig.LOCATORS_NAME, getServerHostName(host) + "[" + // locatorPort + "]"); props.setProperty(LOG_LEVEL, "info"); props.setProperty(STATISTIC_SAMPLING_ENABLED, "true"); props.setProperty(ENABLE_TIME_STATISTICS, "true"); props.put(ENABLE_NETWORK_PARTITION_DETECTION, "true"); return props; } private void lockTheLocks(VM vm0, final InternalDistributedMember member) { vm0.invokeAsync(new SerializableRunnable() { private static final long serialVersionUID = 1L; public void run() { lock.lock(); try { Thread.sleep(1000); } catch (InterruptedException e) { fail("interrupted", e); } ResultCollector collector = FunctionService.onMember(basicGetSystem(), member).execute(new TestFunction()); // wait the function to lock the lock on member. collector.getResult(); lock.unlock(); } }); } private void lockTheDLocks(VM vm, final String first, final String second) { vm.invokeAsync(new SerializableRunnable() { private static final long serialVersionUID = 1L; public void run() { getCache(); DistributedLockService dls = DistributedLockService.create("deadlock_test", getSystem()); dls.lock(first, 10 * 1000, -1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } dls.lock(second, 10 * 1000, -1); } }); } private InternalDistributedMember createCache(VM vm) { return (InternalDistributedMember) vm.invoke(new SerializableCallable() { /** * */ private static final long serialVersionUID = 1L; public Object call() { getCache(); return getSystem().getDistributedMember(); } }); } private String getResultAsString(Result result) { StringBuilder sb = new StringBuilder(); while (result.hasNextLine()) { sb.append(result.nextLine()); } return sb.toString(); } private static class TestFunction implements Function { private static final long serialVersionUID = 1L; private static final int LOCK_WAIT_TIME = 1000; public boolean hasResult() { return true; } public void execute(FunctionContext context) { try { stuckThreads.add(Thread.currentThread()); lock.tryLock(LOCK_WAIT_TIME, TimeUnit.SECONDS); } catch (InterruptedException e) { // ingore } context.getResultSender().lastResult(null); } public String getId() { return getClass().getCanonicalName(); } public boolean optimizeForWrite() { return false; } public boolean isHA() { return false; } } }