/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.calc.runner; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.fail; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.stream.IntStream; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.opengamma.strata.basics.CalculationTarget; import com.opengamma.strata.collect.result.FailureReason; import com.opengamma.strata.collect.result.Result; @Test public class ListenerWrapperTest { // Tests that a listener is only invoked by a single thread at any time even if multiple threads are // invoking the wrapper concurrently. public void concurrentExecution() throws InterruptedException { int nThreads = Runtime.getRuntime().availableProcessors(); int resultsPerThread = 10; ConcurrentLinkedQueue<String> errors = new ConcurrentLinkedQueue<>(); CountDownLatch latch = new CountDownLatch(1); int expectedResultCount = nThreads * resultsPerThread; Listener listener = new Listener(errors, latch); Consumer<CalculationResults> wrapper = new ListenerWrapper(listener, expectedResultCount, ImmutableList.of(), ImmutableList.of()); ExecutorService executor = Executors.newFixedThreadPool(nThreads); CalculationResult result = CalculationResult.of(0, 0, Result.failure(FailureReason.ERROR, "foo")); CalculationTarget target = new CalculationTarget() {}; CalculationResults results = CalculationResults.of(target, ImmutableList.of(result)); IntStream.range(0, expectedResultCount).forEach(i -> executor.submit(() -> wrapper.accept(results))); latch.await(); executor.shutdown(); if (!errors.isEmpty()) { String allErrors = errors.stream().collect(joining("\n")); fail(allErrors); } } public static final class Listener implements CalculationListener { /** * Calling fail() on a different thread from the one running the test won't cause the test to fail. * If any failures occur in the listener the failure message is put on this queue. * The test can check the queue at the end of the test and fail if it is non-empty. */ private final Queue<String> errors; /** Latch that prevents the test method returning until all calculations have completed. */ private final CountDownLatch latch; /** The name of the thread currently invoking this listener. */ private volatile String threadName; public Listener(Queue<String> errors, CountDownLatch latch) { this.errors = errors; this.latch = latch; } @Override public void resultReceived(CalculationTarget target, CalculationResult result) { if (threadName != null) { errors.add("Expected threadName to be null but it was " + threadName); } threadName = Thread.currentThread().getName(); try { // Give other threads a chance to get into this method Thread.sleep(5); } catch (InterruptedException e) { // Won't ever happen } threadName = null; } @Override public void calculationsComplete() { if (threadName != null) { errors.add("Expected threadName to be null but it was " + threadName); } threadName = Thread.currentThread().getName(); try { Thread.sleep(5); } catch (InterruptedException e) { // Won't ever happen } threadName = null; latch.countDown(); } } }