@Test
public void testMetricCacheUpdateRace() throws Exception {

  // Create test source with a single metric counter of value 1.
  TestMetricsSource source = new TestMetricsSource();
  MetricsSourceBuilder sourceBuilder =
      MetricsAnnotations.newSourceBuilder(source);
  final long JMX_CACHE_TTL = 250; // ms
  List<MetricsTag> injectedTags = new ArrayList<MetricsTag>();
  MetricsSourceAdapter sourceAdapter =
      new MetricsSourceAdapter("test", "test",
          "test JMX cache update race condition", sourceBuilder.build(),
          injectedTags, null, null, JMX_CACHE_TTL, false);
  ScheduledExecutorService updaterExecutor =
      Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder().build());
  ScheduledExecutorService readerExecutor =
      Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder().build());
  final AtomicBoolean hasError = new AtomicBoolean(false);
  // Wake up every 1 JMX cache TTL to set lastRecs before updateJmxCache() is
  // called.
  SourceUpdater srcUpdater = new SourceUpdater(sourceAdapter, hasError);
  ScheduledFuture<?> updaterFuture =
      updaterExecutor.scheduleAtFixedRate(srcUpdater,
          sourceAdapter.getJmxCacheTTL(), sourceAdapter.getJmxCacheTTL(),
          TimeUnit.MILLISECONDS);
  srcUpdater.setFuture(updaterFuture);
  // Wake up every 2 JMX cache TTL so updateJmxCache() will try to update
  // JMX cache.
  SourceReader srcReader = new SourceReader(source, sourceAdapter, hasError);
  ScheduledFuture<?> readerFuture =
      readerExecutor.scheduleAtFixedRate(srcReader,
          0, // set JMX info cache at the beginning
          2 * sourceAdapter.getJmxCacheTTL(), TimeUnit.MILLISECONDS);
  srcReader.setFuture(readerFuture);
  // Let the threads do their work.
  Thread.sleep(RACE_TEST_RUNTIME);
  assertFalse("Hit error", hasError.get());
  // cleanup
  updaterExecutor.shutdownNow();
  readerExecutor.shutdownNow();
  updaterExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS);
  readerExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS);
}