/** @throws Exception If failed. */
  public void testTopologyListener() throws Exception {
    final Collection<UUID> added = new ArrayList<>(1);
    final Collection<UUID> rmvd = new ArrayList<>(1);

    final CountDownLatch addedLatch = new CountDownLatch(1);
    final CountDownLatch rmvLatch = new CountDownLatch(1);

    assertEquals(NODES_CNT, client.compute().refreshTopology(false, false).size());

    GridClientTopologyListener lsnr =
        new GridClientTopologyListener() {
          @Override
          public void onNodeAdded(GridClientNode node) {
            added.add(node.nodeId());

            addedLatch.countDown();
          }

          @Override
          public void onNodeRemoved(GridClientNode node) {
            rmvd.add(node.nodeId());

            rmvLatch.countDown();
          }
        };

    client.addTopologyListener(lsnr);

    try {
      Grid g = startGrid(NODES_CNT + 1);

      UUID id = g.localNode().id();

      assertTrue(addedLatch.await(2 * TOP_REFRESH_FREQ, MILLISECONDS));

      assertEquals(1, added.size());
      assertEquals(id, F.first(added));

      stopGrid(NODES_CNT + 1);

      assertTrue(rmvLatch.await(2 * TOP_REFRESH_FREQ, MILLISECONDS));

      assertEquals(1, rmvd.size());
      assertEquals(id, F.first(rmvd));
    } finally {
      client.removeTopologyListener(lsnr);

      stopGrid(NODES_CNT + 1);
    }
  }
  /** {@inheritDoc} */
  @Override
  public boolean await(long timeout, TimeUnit unit) throws GridException {
    initializeLatch();

    try {
      return internalLatch.await(timeout, unit);
    } catch (InterruptedException e) {
      throw new GridInterruptedException(e);
    }
  }
  /** {@inheritDoc} */
  @Override
  public void await() throws GridException {
    initializeLatch();

    try {
      internalLatch.await();
    } catch (InterruptedException e) {
      throw new GridInterruptedException(e);
    }
  }
  /** @throws GridException If operation failed. */
  private void initializeLatch() throws GridException {
    if (initGuard.compareAndSet(false, true)) {
      try {
        internalLatch =
            CU.outTx(
                new Callable<CountDownLatch>() {
                  @Override
                  public CountDownLatch call() throws Exception {
                    GridCacheTx tx =
                        CU.txStartInternal(ctx, latchView, PESSIMISTIC, REPEATABLE_READ);

                    try {
                      GridCacheCountDownLatchValue val = latchView.get(key);

                      if (val == null) {
                        if (log.isDebugEnabled())
                          log.debug("Failed to find count down latch with given name: " + name);

                        assert cnt == 0;

                        return new CountDownLatch(cnt);
                      }

                      tx.commit();

                      return new CountDownLatch(val.get());
                    } finally {
                      tx.end();
                    }
                  }
                },
                ctx);

        if (log.isDebugEnabled()) log.debug("Initialized internal latch: " + internalLatch);
      } finally {
        initLatch.countDown();
      }
    } else {
      try {
        initLatch.await();
      } catch (InterruptedException ignored) {
        throw new GridException("Thread has been interrupted.");
      }

      if (internalLatch == null)
        throw new GridException("Internal latch has not been properly initialized.");
    }
  }