@Test
public void runOutOfOrderConcurrencyTest() {

    ExecutorService tp = Executors.newFixedThreadPool(20);
    try {
        TestConcurrencyObserver tw = new TestConcurrencyObserver();
        // we need Synchronized + SafeSubscriber to handle synchronization plus life-cycle
        Observer<String> w = serializedObserver(new SafeSubscriber<String>(tw));
        Future<?> f1 = tp.submit(new OnNextThread(w, 12000));
        Future<?> f2 = tp.submit(new OnNextThread(w, 5000));
        Future<?> f3 = tp.submit(new OnNextThread(w, 75000));
        Future<?> f4 = tp.submit(new OnNextThread(w, 13500));
        Future<?> f5 = tp.submit(new OnNextThread(w, 22000));
        Future<?> f6 = tp.submit(new OnNextThread(w, 15000));
        Future<?> f7 = tp.submit(new OnNextThread(w, 7500));
        Future<?> f8 = tp.submit(new OnNextThread(w, 23500));
        Future<?> f10 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f1, f2, f3, f4));
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            // ignore
        }
        Future<?> f11 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7));
        Future<?> f12 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7));
        Future<?> f13 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7));
        Future<?> f14 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7));
        // // the next 4 onError events should wait on same as f10
        Future<?> f15 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4));
        Future<?> f16 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4));
        Future<?> f17 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4));
        Future<?> f18 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4));
        waitOnThreads(f1, f2, f3, f4, f5, f6, f7, f8, f10, f11, f12, f13, f14, f15, f16, f17, f18);
        @SuppressWarnings("unused")
        int numNextEvents = tw.assertEvents(null); // no check of type since we don't want to test barging results here, just interleaving behavior
        //            System.out.println("Number of events executed: " + numNextEvents);
    } catch (Throwable e) {
        fail("Concurrency test failed: " + e.getMessage());
        e.printStackTrace();
    } finally {
        tp.shutdown();
        try {
            tp.awaitTermination(5000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}