You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by bu...@apache.org on 2020/10/30 16:32:11 UTC

[geode] 02/03: GEODE-8540: Create new DistributedBlackboard Rule (#5557)

This is an automated email from the ASF dual-hosted git repository.

burcham pushed a commit to branch backport-1-13-GEODE-8652-and-friends
in repository https://gitbox.apache.org/repos/asf/geode.git

commit 9a066e64b1722f914994fecb454a2f67f2a46148
Author: Kirk Lund <kl...@apache.org>
AuthorDate: Wed Sep 30 09:39:30 2020 -0700

    GEODE-8540: Create new DistributedBlackboard Rule (#5557)
    
    Package up DUnitBlackboard as a JUnit Rule named DistributedBlackboard.
    
    (cherry picked from commit 26cb822f2ee467545dd708ecc867cebbd2473c70)
---
 .../dunit/internal/DUnitBlackboardDUnitTest.java   |  75 +++---
 .../DistributedBlackboardDistributedTest.java      | 297 +++++++++++++++++++++
 .../InternalBlackboard.java => Blackboard.java}    |  54 ++--
 .../apache/geode/test/dunit/DUnitBlackboard.java   |  55 ++--
 .../test/dunit/internal/InternalBlackboard.java    |  33 ++-
 .../dunit/internal/InternalBlackboardImpl.java     |  59 ++--
 .../test/dunit/rules/DistributedBlackboard.java    | 138 ++++++++++
 7 files changed, 584 insertions(+), 127 deletions(-)

diff --git a/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java
index ae78247..5e151d7 100755
--- a/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java
+++ b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java
@@ -14,83 +14,70 @@
  */
 package org.apache.geode.test.dunit.internal;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.geode.test.dunit.VM.getVM;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
 
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
 import org.junit.Test;
 
-import org.apache.geode.test.dunit.Host;
 import org.apache.geode.test.dunit.VM;
 
-
+@SuppressWarnings("serial")
 public class DUnitBlackboardDUnitTest extends JUnit4DistributedTestCase {
+
   @Test
-  public void canPassDataBetweenVMs() throws Exception {
+  public void canPassDataBetweenVMs() {
     final String MBOX = "myMailbox";
-    VM vm0 = Host.getHost(0).getVM(0);
-    VM vm1 = Host.getHost(0).getVM(1);
+    VM vm0 = getVM(0);
+    VM vm1 = getVM(1);
 
     vm0.invoke("put data in mailbox", () -> getBlackboard().setMailbox(MBOX, "testing"));
 
-    String result = (String) vm1.invoke("get data from mailbox", () -> {
-      return getBlackboard().getMailbox(MBOX);
-    });
+    String result = vm1.invoke("get data from mailbox", () -> getBlackboard().getMailbox(MBOX));
 
-    assertEquals("testing", result);
+    assertThat(result).isEqualTo("testing");
   }
 
   @Test
-  public void canSignalAnotherVM() throws Exception {
+  public void canSignalAnotherVM() {
     final String GATE = "myGate";
-    VM vm0 = Host.getHost(0).getVM(0);
-    VM vm1 = Host.getHost(0).getVM(1);
+    VM vm0 = getVM(0);
+    VM vm1 = getVM(1);
 
     vm1.invoke("wait on gate not yet signalled", () -> {
-      assertFalse(getBlackboard().isGateSignaled(GATE));
-      try {
-        getBlackboard().waitForGate(GATE, 1, TimeUnit.SECONDS);
-      } catch (TimeoutException e) {
-        // expected
-        return;
-      } catch (InterruptedException e) {
-        fail("unexpected interrupt");
-      }
-      fail("unexpected success");
+      assertThat(getBlackboard().isGateSignaled(GATE)).isFalse();
+
+      Throwable thrown = catchThrowable(() -> {
+        getBlackboard().waitForGate(GATE, 1, SECONDS);
+      });
+
+      assertThat(thrown).isInstanceOf(TimeoutException.class);
     });
 
     vm0.invoke("signal gate", () -> getBlackboard().signalGate(GATE));
 
-    vm1.invoke("wait on gate not yet signalled", () -> {
-      try {
-        getBlackboard().waitForGate(GATE, 1, TimeUnit.SECONDS);
-      } catch (TimeoutException e) {
-        fail("unexpected timeout");
-      } catch (InterruptedException e) {
-        fail("unexpected interrupt");
-      }
-      // success expected
-    });
+    vm1.invoke("wait on gate not yet signalled",
+        () -> getBlackboard().waitForGate(GATE, 1, SECONDS));
   }
 
   @Test
-  public void initBlackboardClearsEverything() throws Exception {
+  public void initBlackboardClearsEverything() {
     for (int i = 0; i < 100; i++) {
       getBlackboard().setMailbox("MBOX" + i, "value" + i);
-      assertEquals("value" + i, getBlackboard().getMailbox("MBOX" + i));
+      assertThat((Object) getBlackboard().getMailbox("MBOX" + i)).isEqualTo("value" + i);
+
       getBlackboard().signalGate("GATE" + i);
-      assertTrue(getBlackboard().isGateSignaled("GATE" + i));
+      assertThat(getBlackboard().isGateSignaled("GATE" + i)).isTrue();
     }
-    Host.getHost(0).getVM(1).invoke("clear blackboard", () -> getBlackboard().initBlackboard());
+
+    getVM(1).invoke("clear blackboard", () -> getBlackboard().initBlackboard());
 
     for (int i = 0; i < 100; i++) {
-      assertNull(getBlackboard().getMailbox("MBOX" + i));
-      assertFalse(getBlackboard().isGateSignaled("GATE" + i));
+      assertThat((Object) getBlackboard().getMailbox("MBOX" + i)).isNull();
+      assertThat(getBlackboard().isGateSignaled("GATE" + i)).isFalse();
     }
   }
 }
diff --git a/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/DistributedBlackboardDistributedTest.java b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/DistributedBlackboardDistributedTest.java
new file mode 100644
index 0000000..ea3ed2e
--- /dev/null
+++ b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/DistributedBlackboardDistributedTest.java
@@ -0,0 +1,297 @@
+/*
+ * 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.dunit.rules.tests;
+
+import static java.util.Arrays.asList;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.geode.test.dunit.VM.getController;
+import static org.apache.geode.test.dunit.VM.getVM;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import java.io.Serializable;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.rules.DistributedBlackboard;
+import org.apache.geode.test.dunit.rules.DistributedRule;
+import org.apache.geode.test.junit.rules.serializable.SerializableTestName;
+
+@SuppressWarnings({"serial", "CodeBlock2Expr"})
+public class DistributedBlackboardDistributedTest implements Serializable {
+
+  @Rule
+  public DistributedRule distributedRule = new DistributedRule();
+  @Rule
+  public DistributedBlackboard blackboard = new DistributedBlackboard();
+  @Rule
+  public SerializableTestName testName = new SerializableTestName();
+
+  @Test
+  public void canPassDataBetweenVMs() {
+    VM vm0 = getVM(0);
+    VM vm1 = getVM(1);
+
+    vm0.invoke("put data in mailbox", () -> blackboard.setMailbox(mailbox(), value()));
+
+    String result = vm1.invoke("get data from mailbox", () -> blackboard.getMailbox(mailbox()));
+
+    assertThat(result).isEqualTo(value());
+  }
+
+  @Test
+  public void canSignalAnotherVM() {
+    VM vm0 = getVM(0);
+    VM vm1 = getVM(1);
+
+    vm1.invoke("wait on gate not yet signalled", () -> {
+      assertThat(blackboard.isGateSignaled(gate())).isFalse();
+
+      Throwable thrown = catchThrowable(() -> {
+        blackboard.waitForGate(gate(), 1, SECONDS);
+      });
+
+      assertThat(thrown).isInstanceOf(TimeoutException.class);
+    });
+
+    vm0.invoke("signal gate", () -> blackboard.signalGate(gate()));
+
+    vm1.invoke("wait on gate not yet signalled", () -> blackboard.waitForGate(gate(), 1, SECONDS));
+  }
+
+  @Test
+  public void initBlackboardClearsEverything() {
+    for (int i = 0; i < 100; i++) {
+      blackboard.setMailbox(mailbox(i), value(i));
+      assertThat((Object) blackboard.getMailbox(mailbox(i))).isEqualTo(value(i));
+
+      blackboard.signalGate(gate(i));
+      assertThat(blackboard.isGateSignaled(gate(i))).isTrue();
+    }
+
+    getVM(1).invoke("clear blackboard", () -> blackboard.initBlackboard());
+
+    for (int i = 0; i < 100; i++) {
+      assertThat((Object) blackboard.getMailbox(mailbox(i))).isNull();
+      assertThat(blackboard.isGateSignaled(gate(i))).isFalse();
+    }
+  }
+
+  @Test
+  public void getMailbox_returnsValueFromSameVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value()));
+
+    getVM(0).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    });
+  }
+
+  @Test
+  public void getMailbox_returnsValueFromOtherVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value()));
+
+    getVM(1).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    });
+  }
+
+  @Test
+  public void setMailbox_overwrites_valueFromSameVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1)));
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(2)));
+
+    getVM(0).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2));
+    });
+  }
+
+  @Test
+  public void setMailbox_overwrites_valueFromOtherVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1)));
+    getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2)));
+
+    getVM(2).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2));
+    });
+  }
+
+  @Test
+  public void getMailbox_returnsValueFromSameVM_afterBouncingVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value()));
+
+    getVM(0).bounceForcibly();
+
+    getVM(0).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    });
+  }
+
+  @Test
+  public void getMailbox_returnsValueFromOtherVM_afterBouncingVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value()));
+
+    getVM(0).bounceForcibly();
+
+    getVM(1).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    });
+  }
+
+  @Test
+  public void setMailbox_overwrites_valueFromSameVM_afterBouncingVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1)));
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(2)));
+
+    getVM(0).bounceForcibly();
+
+    getVM(0).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox(1))).isEqualTo(value(2));
+    });
+  }
+
+  @Test
+  public void setMailbox_overwrites_valueFromOtherVM_afterBouncingFirstVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1)));
+    getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2)));
+
+    getVM(0).bounceForcibly();
+
+    getVM(2).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2));
+    });
+  }
+
+  @Test
+  public void setMailbox_overwrites_valueFromOtherVM_afterBouncingSecondVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1)));
+    getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2)));
+
+    getVM(1).bounceForcibly();
+
+    getVM(2).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2));
+    });
+  }
+
+  @Test
+  public void setMailbox_overwrites_valueFromOtherVM_afterBouncingBothVMs() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1)));
+    getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2)));
+
+    getVM(0).bounceForcibly();
+    getVM(1).bounceForcibly();
+
+    getVM(2).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2));
+    });
+  }
+
+  @Test
+  public void getMailbox_returnsValueFromSameVM_afterBouncingEveryVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value()));
+
+    getVM(0).bounceForcibly();
+    getVM(1).bounceForcibly();
+    getVM(2).bounceForcibly();
+    getVM(3).bounceForcibly();
+
+    getVM(0).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    });
+  }
+
+  @Test
+  public void getMailbox_returnsValueFromOtherVM_afterBouncingEveryVM() {
+    getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value()));
+
+    getVM(0).bounceForcibly();
+    getVM(1).bounceForcibly();
+    getVM(2).bounceForcibly();
+    getVM(3).bounceForcibly();
+
+    getVM(1).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    });
+  }
+
+  @Test
+  public void getMailbox_returnsValueFromControllerVM_afterBouncingEveryVM() {
+    blackboard.setMailbox(mailbox(), value());
+
+    getVM(0).bounceForcibly();
+    getVM(1).bounceForcibly();
+    getVM(2).bounceForcibly();
+    getVM(3).bounceForcibly();
+
+    getVM(3).invoke(() -> {
+      assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    });
+  }
+
+  @Test
+  public void getMailbox_returnsValueInControllerVM_afterBouncingEveryVM() {
+    blackboard.setMailbox(mailbox(), value());
+
+    getVM(0).bounceForcibly();
+    getVM(1).bounceForcibly();
+    getVM(2).bounceForcibly();
+    getVM(3).bounceForcibly();
+
+    assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+  }
+
+  @Test
+  public void getMailbox_returnsValueInEveryVM() {
+    blackboard.setMailbox(mailbox(), value());
+
+    assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+    for (VM vm : asList(getController(), getVM(0), getVM(1), getVM(2), getVM(3))) {
+      vm.invoke(() -> {
+        assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value());
+      });
+    }
+  }
+
+  private String mailbox() {
+    return value("mailbox", 1);
+  }
+
+  private String value() {
+    return value("value", 1);
+  }
+
+  private String gate() {
+    return value("gate", 1);
+  }
+
+  private String mailbox(int count) {
+    return value("mailbox", count);
+  }
+
+  private String value(int count) {
+    return value("value", count);
+  }
+
+  private String gate(int count) {
+    return value("gate", count);
+  }
+
+  private String value(String prefix, int count) {
+    return prefix + "-" + testName.getMethodName() + "-" + count;
+  }
+}
diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/Blackboard.java
old mode 100755
new mode 100644
similarity index 51%
copy from geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java
copy to geode-dunit/src/main/java/org/apache/geode/test/dunit/Blackboard.java
index 24abf4f..2b15ebd
--- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java
+++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/Blackboard.java
@@ -12,66 +12,68 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.test.dunit.internal;
+package org.apache.geode.test.dunit;
 
-import java.io.Serializable;
-import java.rmi.Remote;
-import java.rmi.RemoteException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import org.apache.geode.test.awaitility.GeodeAwaitility;
+
 /**
- * DUnitBlackboard provides mailboxes and synchronization gateways for distributed unit tests.
+ * Blackboard provides mailboxes and synchronization gateways for distributed tests.
+ *
  * <p>
  * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of
  * using static variables in classes. The caveat being that the objects will be serialized using
  * Java serialization.
+ *
  * <p>
- * Gates may be used to synchronize operations between unit test JVMs. Combined with Awaitility
- * these can be used to test for conditions being met, actions having happened, etc.
+ * Gates may be used to synchronize operations between distributed test JVMs. Combined with
+ * Awaitility these can be used to test for conditions being met, actions having happened, etc.
+ *
  * <p>
  * Look for references to the given methods in your IDE for examples.
  */
-public interface InternalBlackboard extends Remote, Serializable {
+public interface Blackboard {
+
   /**
-   * resets the blackboard
+   * Resets the blackboard.
    */
-  void initBlackboard() throws RemoteException;
+  void initBlackboard();
 
   /**
-   * signals a boolean gate
+   * Signals a boolean gate.
    */
-  void signalGate(String gateName) throws RemoteException;
+  void signalGate(String gateName);
 
   /**
-   * wait for a gate to be signaled
+   * Waits at most {@link GeodeAwaitility#getTimeout()} for a gate to be signaled.
    */
-  void waitForGate(String gateName, long timeout, TimeUnit units)
-      throws RemoteException, TimeoutException, InterruptedException;
+  void waitForGate(String gateName) throws TimeoutException, InterruptedException;
 
   /**
-   * clears a gate
+   * Waits at most the specified timeout for a gate to be signaled.
    */
-  void clearGate(String gateName) throws RemoteException;
+  void waitForGate(String gateName, long timeout, TimeUnit units)
+      throws TimeoutException, InterruptedException;
 
   /**
-   * test to see if a gate has been signeled
+   * Clears a gate.
    */
-  boolean isGateSignaled(String gateName) throws RemoteException;
+  void clearGate(String gateName);
 
   /**
-   * put an object into a mailbox slot. The object must be java-serializable
+   * Checks to see if a gate has been signaled.
    */
-  void setMailbox(String boxName, Object value) throws RemoteException;
+  boolean isGateSignaled(String gateName);
 
   /**
-   * retrieve an object from a mailbox slot
+   * Puts an object into a mailbox slot. The object must be java-serializable.
    */
-  <T> T getMailbox(String boxName) throws RemoteException;
+  <T> void setMailbox(String boxName, T value);
 
   /**
-   * ping the blackboard to make sure it's there
+   * Retrieves an object from a mailbox slot.
    */
-  void ping() throws RemoteException;
-
+  <T> T getMailbox(String boxName);
 }
diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java
index d87b99d..1ff5a0a 100755
--- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java
+++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java
@@ -14,6 +14,9 @@
  */
 package org.apache.geode.test.dunit;
 
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.apache.geode.test.awaitility.GeodeAwaitility.getTimeout;
+
 import java.rmi.RemoteException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -22,7 +25,7 @@ import org.apache.geode.test.dunit.internal.InternalBlackboard;
 import org.apache.geode.test.dunit.internal.InternalBlackboardImpl;
 
 /**
- * DUnitBlackboard provides mailboxes and synchronization gateways for distributed unit tests.
+ * DUnitBlackboard provides mailboxes and synchronization gateways for distributed tests.
  *
  * <p>
  * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of
@@ -36,17 +39,19 @@ import org.apache.geode.test.dunit.internal.InternalBlackboardImpl;
  * <p>
  * Look for references to the given methods in your IDE for examples.
  */
-public class DUnitBlackboard {
+public class DUnitBlackboard implements Blackboard {
 
-  private InternalBlackboard blackboard;
+  private final InternalBlackboard blackboard;
 
   public DUnitBlackboard() {
-    blackboard = InternalBlackboardImpl.getInstance();
+    this(InternalBlackboardImpl.getInstance());
+  }
+
+  public DUnitBlackboard(InternalBlackboard blackboard) {
+    this.blackboard = blackboard;
   }
 
-  /**
-   * resets the blackboard
-   */
+  @Override
   public void initBlackboard() {
     try {
       blackboard.initBlackboard();
@@ -55,11 +60,8 @@ public class DUnitBlackboard {
     }
   }
 
-  /**
-   * signals a boolean gate
-   */
+  @Override
   public void signalGate(String gateName) {
-    // System.out.println(Thread.currentThread().getName()+": signaling gate " + gateName);
     try {
       blackboard.signalGate(gateName);
     } catch (RemoteException e) {
@@ -67,12 +69,15 @@ public class DUnitBlackboard {
     }
   }
 
-  /**
-   * wait for a gate to be signaled
-   */
+  @Override
+  public void waitForGate(String gateName)
+      throws TimeoutException, InterruptedException {
+    waitForGate(gateName, getTimeout().toMinutes(), MINUTES);
+  }
+
+  @Override
   public void waitForGate(String gateName, long timeout, TimeUnit units)
       throws TimeoutException, InterruptedException {
-    // System.out.println(Thread.currentThread().getName()+": waiting for gate " + gateName);
     try {
       blackboard.waitForGate(gateName, timeout, units);
     } catch (RemoteException e) {
@@ -80,9 +85,7 @@ public class DUnitBlackboard {
     }
   }
 
-  /**
-   * clear a gate
-   */
+  @Override
   public void clearGate(String gateName) {
     try {
       blackboard.clearGate(gateName);
@@ -91,9 +94,7 @@ public class DUnitBlackboard {
     }
   }
 
-  /**
-   * test to see if a gate has been signeled
-   */
+  @Override
   public boolean isGateSignaled(String gateName) {
     try {
       return blackboard.isGateSignaled(gateName);
@@ -102,9 +103,7 @@ public class DUnitBlackboard {
     }
   }
 
-  /**
-   * put an object into a mailbox slot. The object must be java-serializable
-   */
+  @Override
   public void setMailbox(String boxName, Object value) {
     try {
       blackboard.setMailbox(boxName, value);
@@ -113,9 +112,7 @@ public class DUnitBlackboard {
     }
   }
 
-  /**
-   * retrieve an object from a mailbox slot
-   */
+  @Override
   public <T> T getMailbox(String boxName) {
     try {
       return blackboard.getMailbox(boxName);
@@ -123,4 +120,8 @@ public class DUnitBlackboard {
       throw new RuntimeException("remote call failed", e);
     }
   }
+
+  public InternalBlackboard internal() {
+    return blackboard;
+  }
 }
diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java
index 24abf4f..222a301 100755
--- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java
+++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java
@@ -17,61 +17,70 @@ package org.apache.geode.test.dunit.internal;
 import java.io.Serializable;
 import java.rmi.Remote;
 import java.rmi.RemoteException;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
 /**
- * DUnitBlackboard provides mailboxes and synchronization gateways for distributed unit tests.
+ * InternalBlackboard provides mailboxes and synchronization gateways for distributed tests.
+ *
  * <p>
  * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of
  * using static variables in classes. The caveat being that the objects will be serialized using
  * Java serialization.
+ *
  * <p>
  * Gates may be used to synchronize operations between unit test JVMs. Combined with Awaitility
  * these can be used to test for conditions being met, actions having happened, etc.
- * <p>
- * Look for references to the given methods in your IDE for examples.
  */
 public interface InternalBlackboard extends Remote, Serializable {
+
   /**
-   * resets the blackboard
+   * Resets the blackboard.
    */
   void initBlackboard() throws RemoteException;
 
   /**
-   * signals a boolean gate
+   * Signals a boolean gate.
    */
   void signalGate(String gateName) throws RemoteException;
 
   /**
-   * wait for a gate to be signaled
+   * Waits for a gate to be signaled.
    */
   void waitForGate(String gateName, long timeout, TimeUnit units)
       throws RemoteException, TimeoutException, InterruptedException;
 
   /**
-   * clears a gate
+   * Clears a gate.
    */
   void clearGate(String gateName) throws RemoteException;
 
   /**
-   * test to see if a gate has been signeled
+   * Checks to see if a gate has been signaled.
    */
   boolean isGateSignaled(String gateName) throws RemoteException;
 
   /**
-   * put an object into a mailbox slot. The object must be java-serializable
+   * Puts an object into a mailbox slot. The object must be java-serializable.
    */
-  void setMailbox(String boxName, Object value) throws RemoteException;
+  <T> void setMailbox(String boxName, T value) throws RemoteException;
 
   /**
-   * retrieve an object from a mailbox slot
+   * Retrieves an object from a mailbox slot.
    */
   <T> T getMailbox(String boxName) throws RemoteException;
 
   /**
-   * ping the blackboard to make sure it's there
+   * Pings the blackboard to make sure it's there.
    */
   void ping() throws RemoteException;
 
+  Map<String, Boolean> gates() throws RemoteException;
+
+  Map<String, Serializable> mailboxes() throws RemoteException;
+
+  void putGates(Map<String, Boolean> gates) throws RemoteException;
+
+  void putMailboxes(Map<String, Serializable> mailboxes) throws RemoteException;
 }
diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java
index bbed22e..e24a5a0 100755
--- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java
+++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java
@@ -14,6 +14,12 @@
  */
 package org.apache.geode.test.dunit.internal;
 
+import static java.util.Collections.unmodifiableMap;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
+
+import java.io.Serializable;
+import java.net.MalformedURLException;
 import java.rmi.AlreadyBoundException;
 import java.rmi.Naming;
 import java.rmi.NotBoundException;
@@ -24,26 +30,24 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-
 public class InternalBlackboardImpl extends UnicastRemoteObject implements InternalBlackboard {
-  public static InternalBlackboard blackboard;
 
-  private Map<String, Boolean> gates = new ConcurrentHashMap<>();
-
-  private Map<String, Object> mailboxes = new ConcurrentHashMap();
+  private static InternalBlackboard blackboard;
 
+  private final Map<String, Boolean> gates = new ConcurrentHashMap<>();
+  private final Map<String, Serializable> mailboxes = new ConcurrentHashMap<>();
 
   /**
    * Zero-arg constructor for remote method invocations.
    */
   public InternalBlackboardImpl() throws RemoteException {
-    super();
+    // nothing
   }
 
   /**
    * Creates a singleton event listeners blackboard.
    */
-  public static InternalBlackboard getInstance() {
+  public static synchronized InternalBlackboard getInstance() {
     if (blackboard == null) {
       try {
         initialize();
@@ -56,11 +60,12 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter
     return blackboard;
   }
 
-  private static synchronized void initialize() throws Exception {
+  private static synchronized void initialize()
+      throws AlreadyBoundException, MalformedURLException, RemoteException {
     if (blackboard == null) {
       System.out.println(
           DUnitLauncher.RMI_PORT_PARAM + "=" + System.getProperty(DUnitLauncher.RMI_PORT_PARAM));
-      int namingPort = Integer.getInteger(DUnitLauncher.RMI_PORT_PARAM).intValue();
+      int namingPort = Integer.getInteger(DUnitLauncher.RMI_PORT_PARAM);
       String name = "//localhost:" + namingPort + "/" + "InternalBlackboard";
       try {
         blackboard = (InternalBlackboard) Naming.lookup(name);
@@ -74,8 +79,8 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter
 
   @Override
   public void initBlackboard() throws RemoteException {
-    this.gates.clear();
-    this.mailboxes.clear();
+    gates.clear();
+    mailboxes.clear();
   }
 
   @Override
@@ -90,8 +95,8 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter
 
   @Override
   public void waitForGate(final String gateName, final long timeout, final TimeUnit units)
-      throws RemoteException, TimeoutException, InterruptedException {
-    long giveupTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(timeout, units);
+      throws InterruptedException, RemoteException, TimeoutException {
+    long giveupTime = System.currentTimeMillis() + MILLISECONDS.convert(timeout, units);
     while (System.currentTimeMillis() < giveupTime) {
       Boolean gate = gates.get(gateName);
       if (gate != null && gate) {
@@ -105,17 +110,17 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter
   @Override
   public boolean isGateSignaled(final String gateName) {
     Boolean gate = gates.get(gateName);
-    return (gate != null && gate);
+    return gate != null && gate;
   }
 
   @Override
-  public void setMailbox(String boxName, Object value) {
-    mailboxes.put(boxName, value);
+  public <T> void setMailbox(String boxName, T value) {
+    mailboxes.put(boxName, (Serializable) value);
   }
 
   @Override
-  public Object getMailbox(String boxName) {
-    return mailboxes.get(boxName);
+  public <T> T getMailbox(String boxName) {
+    return uncheckedCast(mailboxes.get(boxName));
   }
 
   @Override
@@ -123,5 +128,23 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter
     // no-op
   }
 
+  @Override
+  public Map<String, Boolean> gates() {
+    return unmodifiableMap(gates);
+  }
 
+  @Override
+  public Map<String, Serializable> mailboxes() {
+    return unmodifiableMap(mailboxes);
+  }
+
+  @Override
+  public void putGates(Map<String, Boolean> gates) {
+    this.gates.putAll(gates);
+  }
+
+  @Override
+  public void putMailboxes(Map<String, Serializable> mailboxes) {
+    this.mailboxes.putAll(mailboxes);
+  }
 }
diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedBlackboard.java
new file mode 100644
index 0000000..8161b6d
--- /dev/null
+++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedBlackboard.java
@@ -0,0 +1,138 @@
+/*
+ * 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.dunit.rules;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.geode.test.dunit.Blackboard;
+import org.apache.geode.test.dunit.DUnitBlackboard;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.internal.InternalBlackboard;
+import org.apache.geode.test.dunit.internal.InternalBlackboardImpl;
+
+/**
+ * DistributedBlackboard provides mailboxes and synchronization gateways for distributed tests.
+ *
+ * <p>
+ * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of
+ * using static variables in classes. The caveat being that the objects will be serialized using
+ * Java serialization.
+ *
+ * <p>
+ * Gates may be used to synchronize operations between distributed test JVMs. Combined with
+ * Awaitility these can be used to test for conditions being met, actions having happened, etc.
+ *
+ * <p>
+ * Look for references to the given methods in your IDE for examples.
+ */
+@SuppressWarnings({"serial", "unused"})
+public class DistributedBlackboard extends AbstractDistributedRule implements Blackboard {
+
+  private static final AtomicReference<DUnitBlackboard> BLACKBOARD = new AtomicReference<>();
+  private static final AtomicReference<InternalBlackboard> INTERNAL = new AtomicReference<>();
+
+  private final Map<Integer, Map<String, Boolean>> keepGates = new ConcurrentHashMap<>();
+  private final Map<Integer, Map<String, Serializable>> keepMailboxes = new ConcurrentHashMap<>();
+
+  @Override
+  protected void before() {
+    invoker().invokeInEveryVMAndController(() -> invokeBefore());
+  }
+
+  @Override
+  protected void after() throws Throwable {
+    invoker().invokeInEveryVMAndController(() -> invokeAfter());
+  }
+
+  @Override
+  protected void afterCreateVM(VM vm) {
+    vm.invoke(() -> invokeBefore());
+  }
+
+  @Override
+  protected void beforeBounceVM(VM vm) {
+    keepGates.put(vm.getId(), vm.invoke(() -> INTERNAL.get().gates()));
+    keepMailboxes.put(vm.getId(), vm.invoke(() -> INTERNAL.get().mailboxes()));
+  }
+
+  @Override
+  protected void afterBounceVM(VM vm) {
+    Map<String, Boolean> keepGatesForVM = keepGates.remove(vm.getId());
+    Map<String, Serializable> keepMailboxesForVM = keepMailboxes.remove(vm.getId());
+
+    vm.invoke(() -> {
+      invokeBefore();
+      INTERNAL.get().putGates(keepGatesForVM);
+      INTERNAL.get().putMailboxes(keepMailboxesForVM);
+    });
+  }
+
+  private void invokeBefore() {
+    InternalBlackboard internalBlackboard = InternalBlackboardImpl.getInstance();
+    INTERNAL.set(internalBlackboard);
+    BLACKBOARD.set(new DUnitBlackboard(internalBlackboard));
+  }
+
+  private void invokeAfter() {
+    BLACKBOARD.set(null);
+    INTERNAL.set(null);
+  }
+
+  @Override
+  public void initBlackboard() {
+    BLACKBOARD.get().initBlackboard();
+  }
+
+  @Override
+  public void signalGate(String gateName) {
+    BLACKBOARD.get().signalGate(gateName);
+  }
+
+  @Override
+  public void waitForGate(String gateName) throws TimeoutException, InterruptedException {
+    BLACKBOARD.get().waitForGate(gateName);
+  }
+
+  @Override
+  public void waitForGate(String gateName, long timeout, TimeUnit units)
+      throws TimeoutException, InterruptedException {
+    BLACKBOARD.get().waitForGate(gateName, timeout, units);
+  }
+
+  @Override
+  public void clearGate(String gateName) {
+    BLACKBOARD.get().clearGate(gateName);
+  }
+
+  @Override
+  public boolean isGateSignaled(String gateName) {
+    return BLACKBOARD.get().isGateSignaled(gateName);
+  }
+
+  @Override
+  public <T> void setMailbox(String boxName, T value) {
+    BLACKBOARD.get().setMailbox(boxName, value);
+  }
+
+  @Override
+  public <T> T getMailbox(String boxName) {
+    return BLACKBOARD.get().getMailbox(boxName);
+  }
+}