private static void testPromiseListenerAddWhenComplete(Throwable cause) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final Promise<Void> promise = new DefaultPromise<Void>(ImmediateEventExecutor.INSTANCE); promise.addListener( new FutureListener<Void>() { @Override public void operationComplete(Future<Void> future) throws Exception { promise.addListener( new FutureListener<Void>() { @Override public void operationComplete(Future<Void> future) throws Exception { latch.countDown(); } }); } }); if (cause == null) { promise.setSuccess(null); } else { promise.setFailure(cause); } latch.await(); }
/** * This test is mean to simulate the following sequence of events, which all take place on the I/O * thread: * * <ol> * <li>A write is done * <li>The write operation completes, and the promise state is changed to done * <li>A listener is added to the return from the write. The {@link * FutureListener#operationComplete()} updates state which must be invoked before the * response to the previous write is read. * <li>The write operation * </ol> */ private static void testLateListenerIsOrderedCorrectly(Throwable cause) throws InterruptedException { final EventExecutor executor = new TestEventExecutor(); try { final AtomicInteger state = new AtomicInteger(); final CountDownLatch latch1 = new CountDownLatch(1); final CountDownLatch latch2 = new CountDownLatch(2); final Promise<Void> promise = new DefaultPromise<Void>(executor); // Add a listener before completion so "lateListener" is used next time we add a listener. promise.addListener( new FutureListener<Void>() { @Override public void operationComplete(Future<Void> future) throws Exception { assertTrue(state.compareAndSet(0, 1)); } }); // Simulate write operation completing, which will execute listeners in another thread. if (cause == null) { promise.setSuccess(null); } else { promise.setFailure(cause); } // Add a "late listener" promise.addListener( new FutureListener<Void>() { @Override public void operationComplete(Future<Void> future) throws Exception { assertTrue(state.compareAndSet(1, 2)); latch1.countDown(); } }); // Wait for the listeners and late listeners to be completed. latch1.await(); assertEquals(2, state.get()); // This is the important listener. A late listener that is added after all late listeners // have completed, and needs to update state before a read operation (on the same executor). executor.execute( new Runnable() { @Override public void run() { promise.addListener( new FutureListener<Void>() { @Override public void operationComplete(Future<Void> future) throws Exception { assertTrue(state.compareAndSet(2, 3)); latch2.countDown(); } }); } }); // Simulate a read operation being queued up in the executor. executor.execute( new Runnable() { @Override public void run() { // This is the key, we depend upon the state being set in the next listener. assertEquals(3, state.get()); latch2.countDown(); } }); latch2.await(); } finally { executor.shutdownGracefully(0, 0, TimeUnit.SECONDS).sync(); } }