public void testCommitDoesntWriteAfterRollback() throws Exception {
    // Start a tx on A: put(k, v1), owners(k) = [B (primary) and C (backup)]
    // Block the commit on C so that it times out
    // Wait for the rollback command to be executed on B and C, and for the tx to end
    // Check that locks are released on B
    // Start another transaction on A: put(k, v2) with the same key
    // Check that the new transaction writes successfully
    // Allow the commit to proceed on C
    // Check that k=v2 everywhere
    StateSequencer sequencer = new StateSequencer();
    sequencer.logicalThread(
        "tx1",
        "tx1:begin",
        "tx1:block_commit_on_backup",
        "tx1:after_rollback_on_primary",
        "tx1:after_rollback_on_backup",
        "tx1:resume_commit_on_backup",
        "tx1:after_commit_on_backup",
        "tx1:check");
    sequencer.logicalThread("tx2", "tx2:begin", "tx2:end");

    sequencer.order(
        "tx1:after_rollback_on_backup", "tx2:begin", "tx2:end", "tx1:resume_commit_on_backup");

    advanceOnInterceptor(
            sequencer,
            cache(2),
            StateTransferInterceptor.class,
            matchCommand(CommitCommand.class).matchCount(0).build())
        .before("tx1:block_commit_on_backup", "tx1:resume_commit_on_backup")
        .after("tx1:after_commit_on_backup");

    advanceOnInterceptor(
            sequencer,
            cache(1),
            StateTransferInterceptor.class,
            matchCommand(RollbackCommand.class).build())
        .after("tx1:after_rollback_on_primary");

    advanceOnInterceptor(
            sequencer,
            cache(2),
            StateTransferInterceptor.class,
            matchCommand(RollbackCommand.class).build())
        .after("tx1:after_rollback_on_backup");

    assertEquals(
        Arrays.asList(address(1), address(2)),
        advancedCache(0).getDistributionManager().locate(TEST_KEY));
    sequencer.advance("tx1:begin");

    tm(0).begin();
    cache(0).put(TEST_KEY, TX1_VALUE);
    try {
      tm(0).commit();
    } catch (RollbackException e) {
      log.debugf("Commit timed out as expected", e);
    }

    sequencer.advance("tx2:begin");
    LockManager lockManager1 = TestingUtil.extractLockManager(cache(1));
    assertFalse(lockManager1.isLocked(TEST_KEY));

    tm(0).begin();
    cache(0).put(TEST_KEY, TX2_VALUE);
    tm(0).commit();

    checkValue();
    sequencer.advance("tx2:end");

    sequencer.advance("tx1:check");
    checkValue();
  }
  @Test(
      enabled = false,
      description = "Fix for this scenario is not implemented yet - rollback is asynchronous")
  public void testCommitDoesntWriteAfterTxEnd() throws Exception {
    // Start a tx on A: put(k, v1), owners(k) = [B (primary) and C (backup)]
    // Block the commit on C so that it times out
    // Wait for the rollback command to be executed on B and block before it executes on C
    // Check that k is still locked on B
    // Allow the commit to proceed on C
    // Allow the rollback to proceed on C
    // Check that k=v1 everywhere
    // Check that locks are released on B
    final StateSequencer sequencer = new StateSequencer();
    sequencer.logicalThread(
        "tx1",
        "tx1:begin",
        "tx1:block_commit_on_backup",
        "tx1:after_rollback_on_primary",
        "tx1:block_rollback_on_backup",
        "tx1:resume_commit_on_backup",
        "tx1:after_commit_on_backup",
        "tx1:resume_rollback_on_backup",
        "tx1:after_rollback_on_backup",
        "tx1:check");

    advanceOnInterceptor(
            sequencer,
            cache(2),
            StateTransferInterceptor.class,
            matchCommand(CommitCommand.class).matchCount(0).build())
        .before("tx1:block_commit_on_backup", "tx1:resume_commit_on_backup")
        .after("tx1:after_commit_on_backup");

    advanceOnInterceptor(
            sequencer,
            cache(1),
            StateTransferInterceptor.class,
            matchCommand(RollbackCommand.class).build())
        .after("tx1:after_rollback_on_primary");

    advanceOnInterceptor(
            sequencer,
            cache(2),
            StateTransferInterceptor.class,
            matchCommand(RollbackCommand.class).build())
        .before("tx1:block_rollback_on_backup")
        .after("tx1:after_rollback_on_backup");

    assertEquals(
        Arrays.asList(address(1), address(2)),
        advancedCache(0).getDistributionManager().locate(TEST_KEY));
    Future<Object> lockCheckFuture =
        fork(
            new Callable<Object>() {
              @Override
              public Object call() throws Exception {
                sequencer.enter("tx1:resume_rollback_on_backup");
                try {
                  assertTrue(TestingUtil.extractLockManager(cache(1)).isLocked(TEST_KEY));
                } finally {
                  sequencer.exit("tx1:resume_rollback_on_backup");
                }
                return null;
              }
            });

    sequencer.advance("tx1:begin");

    tm(0).begin();
    cache(0).put(TEST_KEY, TX1_VALUE);
    tm(0).commit();

    sequencer.advance("tx1:check");
    assertFalse(TestingUtil.extractLockManager(cache(1)).isLocked(TEST_KEY));
    lockCheckFuture.get(10, TimeUnit.SECONDS);
  }