public boolean commitAt(final long firstLogIndex, final long lastLogIndex, final PeerId peer) {
        // TODO  use lock-free algorithm here?
        final long stamp = this.stampedLock.writeLock();
        long lastCommittedIndex = 0;
        try {
            if (this.pendingIndex == 0) {
                return false;
            }
            if (lastLogIndex < this.pendingIndex) {
                return true;
            }

            if (lastLogIndex >= this.pendingIndex + this.pendingMetaQueue.size()) {
                throw new ArrayIndexOutOfBoundsException();
            }

            final long startAt = Math.max(this.pendingIndex, firstLogIndex);
            Ballot.PosHint hint = new Ballot.PosHint();
            for (long logIndex = startAt; logIndex <= lastLogIndex; logIndex++) {
                final Ballot bl = this.pendingMetaQueue.get((int) (logIndex - this.pendingIndex));
                hint = bl.grant(peer, hint);
                if (bl.isGranted()) {
                    lastCommittedIndex = logIndex;
                }
            }
            if (lastCommittedIndex == 0) {
                return true;
            }
            // When removing a peer off the raft group which contains even number of
            // peers, the quorum would decrease by 1, e.g. 3 of 4 changes to 2 of 3. In
            // this case, the log after removal may be committed before some previous
            // logs, since we use the new configuration to deal the quorum of the
            // removal request, we think it's safe to commit all the uncommitted
            // previous logs, which is not well proved right now
            this.pendingMetaQueue.removeFromFirst((int) (lastCommittedIndex - this.pendingIndex) + 1);
            LOG.debug("Node {} committed log fromIndex={}, toIndex={}.", this.opts.getNodeId(), this.pendingIndex,
                lastCommittedIndex);
            this.pendingIndex = lastCommittedIndex + 1;
            this.lastCommittedIndex = lastCommittedIndex;
        } finally {
            this.stampedLock.unlockWrite(stamp);
        }
        this.waiter.onCommitted(lastCommittedIndex);
        return true;
    }
@Test
    public void testCommitAt() {
        assertFalse(this.box.commitAt(1, 3, new PeerId("localhost", 8081)));
        assertTrue(box.resetPendingIndex(1));
        assertTrue(this.box.appendPendingTask(
            JRaftUtils.getConfiguration("localhost:8081,localhost:8082,localhost:8083"),
            JRaftUtils.getConfiguration("localhost:8081"), new Closure() {

                @Override
                public void run(Status status) {

                }
            }));
        assertEquals(0, this.box.getLastCommittedIndex());
        try {
            this.box.commitAt(1, 3, new PeerId("localhost", 8081));
            fail();
        } catch (ArrayIndexOutOfBoundsException e) {

        }
        assertTrue(this.box.commitAt(1, 1, new PeerId("localhost", 8081)));
        assertEquals(0, this.box.getLastCommittedIndex());
        assertEquals(1, this.box.getPendingIndex());
        assertTrue(this.box.commitAt(1, 1, new PeerId("localhost", 8082)));
        assertEquals(1, this.box.getLastCommittedIndex());
        assertEquals(2, this.box.getPendingIndex());
        Mockito.verify(this.waiter, Mockito.only()).onCommitted(1);
    }