You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ds...@apache.org on 2017/08/31 01:26:19 UTC
[42/47] geode git commit: GEODE-2859: Fix race condition in
ShowDeadlockDUnitTest
GEODE-2859: Fix race condition in ShowDeadlockDUnitTest
Project: http://git-wip-us.apache.org/repos/asf/geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/ca0dca51
Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/ca0dca51
Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/ca0dca51
Branch: refs/heads/feature/GEODE-3543
Commit: ca0dca51ebd9694cf6a34548252ed3c720b59253
Parents: fb9a405
Author: Jared Stewart <js...@pivotal.io>
Authored: Tue Aug 29 12:02:47 2017 -0700
Committer: Jared Stewart <js...@pivotal.io>
Committed: Wed Aug 30 09:44:59 2017 -0700
----------------------------------------------------------------------
.../cli/commands/ShowDeadlockDUnitTest.java | 205 +++++++------------
.../dunit/rules/GfshShellConnectionRule.java | 9 +-
geode-junit/build.gradle | 1 +
.../concurrent/FileBasedCountDownLatch.java | 87 ++++++++
.../concurrent/FileBasedCountDownLatchTest.java | 37 ++++
5 files changed, 202 insertions(+), 137 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/geode/blob/ca0dca51/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/ShowDeadlockDUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/ShowDeadlockDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/ShowDeadlockDUnitTest.java
index 4df0b96..51560db 100755
--- a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/ShowDeadlockDUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/ShowDeadlockDUnitTest.java
@@ -14,203 +14,138 @@
*/
package org.apache.geode.management.internal.cli.commands;
-import static org.apache.geode.test.dunit.Assert.assertEquals;
-import static org.apache.geode.test.dunit.Assert.assertTrue;
-import static org.apache.geode.test.dunit.Invoke.invokeInEveryVM;
+import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
-import org.apache.commons.io.FileUtils;
import org.awaitility.Awaitility;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
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.cache30.CacheTestCase;
-import org.apache.geode.distributed.internal.deadlock.GemFireDeadlockDetector;
import org.apache.geode.distributed.internal.deadlock.GemFireDeadlockDetectorDUnitTest;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
-import org.apache.geode.management.cli.CommandStatement;
-import org.apache.geode.management.cli.Result;
-import org.apache.geode.management.cli.Result.Status;
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.VM;
+import org.apache.geode.test.concurrent.FileBasedCountDownLatch;
+import org.apache.geode.test.dunit.rules.GfshShellConnectionRule;
+import org.apache.geode.test.dunit.rules.LocatorServerStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.junit.categories.DistributedTest;
-import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
/**
- *
+ * Distributed tests for show deadlock command in {@link ShowDeadlockCommand}.
+ *
* @see GemFireDeadlockDetectorDUnitTest
*/
@Category(DistributedTest.class)
-public class ShowDeadlockDUnitTest extends CacheTestCase {
+public class ShowDeadlockDUnitTest {
+ private static Thread stuckThread = null;
+ private static final Lock LOCK = new ReentrantLock();
- private static final Set<Thread> stuckThreads =
- Collections.synchronizedSet(new HashSet<Thread>());
+ private MemberVM server1;
+ private MemberVM server2;
- private static final Lock lock = new ReentrantLock();
+ private File outputFile;
+ private String showDeadlockCommand;
- private transient VM vm0;
- private transient VM vm1;
+ @Rule
+ public LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
- private transient InternalDistributedMember member0;
- private transient InternalDistributedMember member1;
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
- public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
+ public GfshShellConnectionRule gfsh = new GfshShellConnectionRule();
@Before
- public void setup() {
- Host host = Host.getHost(0);
- vm0 = host.getVM(0);
- vm1 = host.getVM(1);
-
- // Make sure a deadlock from a previous test is cleared.
- disconnectAllFromDS();
+ public void setup() throws Exception {
+ outputFile = new File(temporaryFolder.getRoot(), "dependency.txt").getAbsoluteFile();
+ showDeadlockCommand = "show dead-locks --file=" + outputFile.getAbsolutePath();
+ outputFile.delete();
- member0 = createCache(vm0);
- member1 = createCache(vm1);
+ MemberVM locator = lsRule.startLocatorVM(0);
+ server1 = lsRule.startServerVM(1, locator.getPort());
+ server2 = lsRule.startServerVM(2, locator.getPort());
- createCache(new Properties());
+ gfsh.connect(locator);
}
@After
- public void teardown() {
- disconnectAllFromDS();
- }
-
- @Override
- public final void preTearDownCacheTestCase() throws Exception {
- invokeInEveryVM(() -> stuckThreads.forEach(Thread::interrupt));
+ public final void after() throws Exception {
+ server1.invoke(() -> stuckThread.interrupt());
+ server2.invoke(() -> stuckThread.interrupt());
}
@Test
public void testNoDeadlock() throws Exception {
- GemFireDeadlockDetector detect = new GemFireDeadlockDetector();
- assertEquals(null, detect.find().findCycle());
-
- File outputFile = new File(temporaryFolder.getRoot(), "dependency.txt");
+ gfsh.executeAndVerifyCommand(showDeadlockCommand);
+ String commandOutput = gfsh.getGfshOutput();
- String showDeadlockCommand = new CommandStringBuilder(CliStrings.SHOW_DEADLOCK)
- .addOption(CliStrings.SHOW_DEADLOCK__DEPENDENCIES__FILE, outputFile.getName()).toString();
-
- Result result = new CommandProcessor()
- .createCommandStatement(showDeadlockCommand, Collections.emptyMap()).process();
- String commandOutput = getResultAsString(result);
-
- assertEquals(true, result.hasIncomingFiles());
- assertEquals(true, result.getStatus().equals(Status.OK));
- assertEquals(true, commandOutput.startsWith(CliStrings.SHOW_DEADLOCK__NO__DEADLOCK));
- result.saveIncomingFiles(temporaryFolder.getRoot().getAbsolutePath());
- assertTrue(outputFile.exists());
+ assertThat(commandOutput).startsWith(CliStrings.SHOW_DEADLOCK__NO__DEADLOCK);
+ assertThat(outputFile).exists();
}
@Test
public void testDistributedDeadlockWithFunction() throws Exception {
- // Have two threads lock locks on different members in different orders.
- // This thread locks the lock member0 first, then member1.
- lockTheLocks(vm0, member1);
- // This thread locks the lock member1 first, then member0.
- lockTheLocks(vm1, member0);
-
- File outputFile = new File(temporaryFolder.getRoot(), "dependency.txt");
-
- String showDeadlockCommand = new CommandStringBuilder(CliStrings.SHOW_DEADLOCK)
- .addOption(CliStrings.SHOW_DEADLOCK__DEPENDENCIES__FILE, outputFile.getName()).toString();
- CommandStatement showDeadlocksCommand =
- new CommandProcessor().createCommandStatement(showDeadlockCommand, Collections.emptyMap());
-
- Awaitility.await().atMost(1, TimeUnit.MINUTES).until(() -> {
- FileUtils.deleteQuietly(outputFile);
- Result result = showDeadlocksCommand.process();
- try {
- result.saveIncomingFiles(temporaryFolder.getRoot().getAbsolutePath());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- String commandOutput = getResultAsString(result);
- assertEquals(true, commandOutput.startsWith(CliStrings.SHOW_DEADLOCK__DEADLOCK__DETECTED));
- assertEquals(true, result.getStatus().equals(Status.OK));
- assertTrue(outputFile.exists());
+ FileBasedCountDownLatch countDownLatch = new FileBasedCountDownLatch(2);
+
+ // This thread locks the lock in server1 first, then server2.
+ lockTheLocks(server1, server2, countDownLatch);
+ // This thread locks the lock server2 first, then server1.
+ lockTheLocks(server2, server1, countDownLatch);
+
+ Awaitility.await().atMost(5, TimeUnit.MINUTES).pollDelay(5, TimeUnit.SECONDS).until(() -> {
+ gfsh.executeAndVerifyCommand(showDeadlockCommand);
+ String commandOutput = gfsh.getGfshOutput();
+ assertThat(commandOutput).startsWith(CliStrings.SHOW_DEADLOCK__DEADLOCK__DETECTED);
+ assertThat(outputFile).exists();
});
}
- private void createCache(Properties props) {
- getSystem(props);
- getCache();
- }
-
- private void lockTheLocks(VM vm0, final InternalDistributedMember member) {
- vm0.invokeAsync(() -> {
- lock.lock();
-
- ResultCollector collector = FunctionService.onMember(member).execute(new TestFunction());
- // wait the function to lock the lock on member.
- collector.getResult();
- lock.unlock();
+ private void lockTheLocks(MemberVM thisVM, final MemberVM thatVM,
+ FileBasedCountDownLatch countDownLatch) {
+ thisVM.invokeAsync(() -> {
+ LOCK.lock();
+ countDownLatch.countDown();
+ countDownLatch.await();
+ // At this point each VM will hold its own lock.
+ lockRemoteVM(thatVM);
+ LOCK.unlock();
});
}
- private InternalDistributedMember createCache(VM vm) {
- return (InternalDistributedMember) vm.invoke(new SerializableCallable<Object>() {
- @Override
- public Object call() {
- getCache();
- return getSystem().getDistributedMember();
- }
- });
- }
+ private static void lockRemoteVM(MemberVM vmToLock) {
+ InternalDistributedMember thatInternalMember = getInternalDistributedMember(vmToLock);
- private String getResultAsString(Result result) {
- StringBuilder sb = new StringBuilder();
- while (result.hasNextLine()) {
- sb.append(result.nextLine());
- }
-
- return sb.toString();
+ ResultCollector collector =
+ FunctionService.onMember(thatInternalMember).execute(new LockFunction());
+ collector.getResult();
}
- private static class TestFunction implements Function<Object> {
- private static final int LOCK_WAIT_TIME = 1000;
-
- @Override
- public boolean hasResult() {
- return true;
- }
+ private static InternalDistributedMember getInternalDistributedMember(MemberVM memberVM) {
+ return memberVM.getVM().invoke(() -> LocatorServerStartupRule.serverStarter.getCache()
+ .getInternalDistributedSystem().getDistributedMember());
+ }
+ private static class LockFunction implements Function<Object> {
@Override
public void execute(FunctionContext<Object> context) {
+ stuckThread = Thread.currentThread();
try {
- stuckThreads.add(Thread.currentThread());
- lock.tryLock(LOCK_WAIT_TIME, TimeUnit.SECONDS);
- } catch (InterruptedException ignored) {
- // ignored
+ LOCK.tryLock(5, TimeUnit.MINUTES);
+ } catch (InterruptedException e) {
+ context.getResultSender().lastResult(null);
}
- context.getResultSender().lastResult(null);
- }
-
- @Override
- public boolean isHA() {
- return false;
}
}
}
http://git-wip-us.apache.org/repos/asf/geode/blob/ca0dca51/geode-core/src/test/java/org/apache/geode/test/dunit/rules/GfshShellConnectionRule.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/GfshShellConnectionRule.java b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/GfshShellConnectionRule.java
index e7f17ef..a9ce889 100644
--- a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/GfshShellConnectionRule.java
+++ b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/GfshShellConnectionRule.java
@@ -223,8 +223,13 @@ public class GfshShellConnectionRule extends DescribedExternalResource {
}
- public CommandResult executeAndVerifyCommand(String command) throws Exception {
- CommandResult result = executeCommand(command);
+ public CommandResult executeAndVerifyCommand(String command) {
+ CommandResult result = null;
+ try {
+ result = executeCommand(command);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
assertThat(result.getStatus())
.describedAs("Failure in command: " + command + "\n Result " + result)
.isEqualTo(Result.Status.OK);
http://git-wip-us.apache.org/repos/asf/geode/blob/ca0dca51/geode-junit/build.gradle
----------------------------------------------------------------------
diff --git a/geode-junit/build.gradle b/geode-junit/build.gradle
index 7c533ad..ccfbb24 100755
--- a/geode-junit/build.gradle
+++ b/geode-junit/build.gradle
@@ -23,6 +23,7 @@ dependencies {
compile 'commons-io:commons-io:' + project.'commons-io.version'
compile 'commons-lang:commons-lang:' + project.'commons-lang.version'
compile 'com.google.guava:guava:' + project.'guava.version'
+ compile 'org.awaitility:awaitility:' + project.'awaitility.version'
compile('junit:junit:' + project.'junit.version') {
http://git-wip-us.apache.org/repos/asf/geode/blob/ca0dca51/geode-junit/src/main/java/org/apache/geode/test/concurrent/FileBasedCountDownLatch.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/concurrent/FileBasedCountDownLatch.java b/geode-junit/src/main/java/org/apache/geode/test/concurrent/FileBasedCountDownLatch.java
new file mode 100644
index 0000000..43fe260
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/concurrent/FileBasedCountDownLatch.java
@@ -0,0 +1,87 @@
+/*
+ * 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.test.concurrent;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.Charsets;
+import org.apache.commons.io.FileUtils;
+import org.awaitility.Awaitility;
+
+/**
+ * This is an implementation of CountDownLatch that can be serialized and used across multiple DUnit
+ * VMs. File locks are used to synchronize between the separate VMs. For an example usage, see
+ * ShowDeadlockDUnitTest.
+ */
+public class FileBasedCountDownLatch implements Serializable {
+ private final File lockFile;
+ private final File dataFile;
+
+ public FileBasedCountDownLatch(int count) throws IOException {
+ lockFile = File.createTempFile("CountDownLatchLock", ".txt");
+ dataFile = File.createTempFile("CountDownLatchData", ".txt");
+
+ try (FileOutputStream out = new FileOutputStream(lockFile)) {
+ java.nio.channels.FileLock lock = out.getChannel().lock();
+ try {
+ FileUtils.writeStringToFile(dataFile, String.valueOf(count), Charsets.UTF_8);
+ } finally {
+ lock.release();
+ }
+ }
+
+ lockFile.deleteOnExit();
+ }
+
+ public void countDown() throws IOException {
+ try (FileOutputStream out = new FileOutputStream(lockFile)) {
+ java.nio.channels.FileLock lock = out.getChannel().lock();
+
+ try {
+ String fileContents = FileUtils.readFileToString(dataFile, Charsets.UTF_8);
+ int currentValue = Integer.valueOf(fileContents);
+
+ int newValue = currentValue - 1;
+ FileUtils.writeStringToFile(dataFile, String.valueOf(newValue), Charsets.UTF_8);
+
+ } finally {
+ lock.release();
+ }
+ }
+ }
+
+ public void await() throws IOException {
+ Awaitility.await().atMost(10, TimeUnit.MINUTES).until(this::currentValue, is(equalTo(0)));
+ }
+
+ protected int currentValue() throws IOException {
+ try (FileOutputStream out = new FileOutputStream(lockFile)) {
+ java.nio.channels.FileLock lock = out.getChannel().lock();
+ try {
+ String fileContents = FileUtils.readFileToString(dataFile, Charsets.UTF_8);
+ return Integer.valueOf(fileContents);
+ } finally {
+ lock.release();
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/ca0dca51/geode-junit/src/test/java/org/apache/geode/test/concurrent/FileBasedCountDownLatchTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/concurrent/FileBasedCountDownLatchTest.java b/geode-junit/src/test/java/org/apache/geode/test/concurrent/FileBasedCountDownLatchTest.java
new file mode 100644
index 0000000..a4da5fb
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/concurrent/FileBasedCountDownLatchTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.test.concurrent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class FileBasedCountDownLatchTest {
+ @Test
+ public void singleThreadedBehaviorIsCorrect() throws Exception {
+ FileBasedCountDownLatch fileBasedCountDownLatch = new FileBasedCountDownLatch(2);
+ assertThat(fileBasedCountDownLatch.currentValue()).isEqualTo(2);
+
+ fileBasedCountDownLatch.countDown();
+ assertThat(fileBasedCountDownLatch.currentValue()).isEqualTo(1);
+
+ fileBasedCountDownLatch.countDown();
+ assertThat(fileBasedCountDownLatch.currentValue()).isEqualTo(0);
+ }
+}