@FixFor( {"MODE-1498", "MODE-2202"} )

@Test
public void shouldWorkWithUserDefinedTransactionsThatUseRollbackInSeparateThreads() throws Exception {
    CyclicBarrier barrier = new CyclicBarrier(2);
    CyclicBarrier completionBarrier = new CyclicBarrier(3);
    session = createSession();
    // THREAD 1 ...
    SessionWorker worker1 = new SessionWorker(session, barrier, completionBarrier, "thread 1") {
        @Override
        protected void execute( Session session ) throws Exception {
            // STEP 1: Setup the listener, and check that the new nodes are not visible to the other session ...
            SimpleListener listener = addListener(3); // we'll create 3 nodes ...
            assertThat(listener.getActualEventCount(), is(0));
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
            barrier.await();
            // STEP 2: Make changes using a transaction, but do not commit the transaction ...
            final TransactionManager txnMgr = getTransactionManager();
            txnMgr.begin();
            assertThat(listener.getActualEventCount(), is(0));
            Node txnNode1 = session.getRootNode().addNode("txnNode1");
            Node txnNode1a = txnNode1.addNode("txnNodeA");
            assertThat(txnNode1a, is(notNullValue()));
            assertThat(session.getRootNode().hasNode("txnNode1"), is(true));
            assertThat(session.getRootNode().hasNode("txnNode2"), is(false));
            session.save();
            assertThat(session.getRootNode().hasNode("txnNode1"), is(true));
            assertThat(session.getRootNode().hasNode("txnNode2"), is(false));
            assertThat(listener.getActualEventCount(), is(0));
            barrier.await();
            // STEP 3: Wait for other session to verify that it can't see the new node we created but have not committed...
            // Meanwhile, sleep a bit to let any incorrect events propagate through the system. Our listener still should
            // not see the event, since we didn't commit ...
            Thread.sleep(100L);
            assertThat(listener.getActualEventCount(), is(0));
            barrier.await();
            // STEP 4: Create another new node using this transaction ...
            Node txnNode2 = session.getRootNode().addNode("txnNode2");
            assertThat(txnNode2, is(notNullValue()));
            session.save();
            assertThat(listener.getActualEventCount(), is(0));
            assertThat(session.getRootNode().hasNode("txnNode1"), is(true));
            assertThat(session.getRootNode().hasNode("txnNode2"), is(true));
            barrier.await();
            // STEP 5: Wait for other session to verify that it can't see the new node we created but have not committed...
            // Meanwhile, sleep a bit to let any incorrect events propagate through the system. Our listener still should
            // not see the event, since we didn't commit ...
            Thread.sleep(100L);
            assertThat(listener.getActualEventCount(), is(0));
            assertThat(session.getRootNode().hasNode("txnNode1"), is(true));
            assertThat(session.getRootNode().hasNode("txnNode2"), is(true));
            barrier.await();
            // STEP 6: Rollback the txn ...
            txnMgr.rollback();
            barrier.await();
            // STEP 7: Check that there were no events and that the nodes are not visible anymore ...
            Thread.sleep(100L);
            assertThat(listener.getActualEventCount(), is(0));
            assertThat(session.getRootNode().hasNode("txnNode1"), is(false));
            assertThat(session.getRootNode().hasNode("txnNode2"), is(false));
            session.refresh(false);
            assertThat(session.getRootNode().hasNode("txnNode1"), is(false));
            assertThat(session.getRootNode().hasNode("txnNode2"), is(false));
        }
    };
    // THREAD 2 ...
    SessionWorker worker2 = new SessionWorker(createSession(), barrier, completionBarrier, "thread 2") {
        @Override
        protected void execute( Session session ) throws Exception {
            // STEP 1: Check that the new nodes are not visible to the other session ...
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
            barrier.await();
            // STEP 2: Wait for changes to be made in the other transaction ...
            Thread.sleep(50L);
            barrier.await();
            // STEP 3: Check that we cannot see the new node created by the other session in the still-ongoing txn ...
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
            session.refresh(false);
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
            barrier.await();
            // STEP 4: Wait for changes to be made in the other transaction ...
            Thread.sleep(50L);
            barrier.await();
            // STEP 5: Check that we cannot see the new node created by the other session in the still-ongoing txn ...
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
            session.refresh(false);
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
            barrier.await();
            // STEP 6: Wait for the transaction to be rolled back ...
            Thread.sleep(50L);
            barrier.await();
            // STEP 7: Check that the node IS NOT visible to this session ...
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
            session.refresh(false);
            nodeDoesNotExist(session, "/", "txnNode1");
            nodeDoesNotExist(session, "/", "txnNode2");
        }
    };
    new Thread(worker1).start();
    new Thread(worker2).start();
    // Wait for the threads to complete ...
    completionBarrier.await(10, TimeUnit.SECONDS);
}