@Override
            public void run2() {
              final Region<Object, Object> region = getCache().getRegion("root" + SEPARATOR + name);
              final Object[] valueHolder = new Object[1];

              // wait for vm1 to cause the loader to be invoked
              logger.info("t2 is waiting for loader to be invoked by t1");
              try {
                loaderInvokedLatch.await(30, TimeUnit.SECONDS);
              } catch (InterruptedException e) {
                fail("interrupted");
              }
              await().until(() -> loaderInvoked);

              Thread t = new Thread("invoke get()") {
                @Override
                public void run() {
                  try {
                    valueHolder[0] = region.get(objectName);
                  } catch (RuntimeException e) {
                    valueHolder[0] = e;
                  }
                }
              };

              t.setDaemon(true);
              t.start();
              try {
                // let the thread get to the point of blocking on vm1's Future
                // in LocalRegion.nonTxnFindObject()
                Thread.sleep(5000);
              } catch (InterruptedException e) {
                fail("interrupted");
              }

              readyForExceptionLatch.countDown();
              try {
                t.join(30000);
              } catch (InterruptedException e) {
                fail("interrupted");
              }
              if (t.isAlive()) {
                t.interrupt();
                fail("get() operation blocked for too long - test needs some work");
              }

              logger.info("t2 is invoking get(" + objectName + ")");
              Object value = valueHolder[0];
              if (value instanceof RuntimeException) {
                if (((Exception) value).getMessage().contains(exceptionString)) {
                  fail("second load should not have thrown an exception");
                } else {
                  throw (RuntimeException) value;
                }
              } else {
                logger.info("t2 retrieved value " + value);
                assertThat(value).isNotNull();
              }
            }
This test is for a bug in which a cache loader threw an exception that caused the wrong value
to be put in a Future in nonTxnFindObject. This in turn caused a concurrent search for the
object to not invoke the loader a second time.
<p>
VM0 is used to create a cache and a region having a loader that simulates the conditions that
caused the bug. One async thread then does a get() which invokes the loader. Another async
thread does a get() which reaches nonTxnFindObject and blocks waiting for the first thread's
load to complete. The loader then throws an exception that is sent back to the first thread.
The second thread should then cause the loader to be invoked again, and this time the loader
will return a value. Both threads then validate that they received the expected result.
geode-core/src/distributedTest/java/org/apache/geode/cache30/SearchAndLoadDUnitTest.java