for (Map.Entry<MailItem, Object> entry : itemsToIndex.entrySet()) {
for (Map.Entry<MailItem, MailboxChange.IndexItemEntry> entry : itemsToIndex.entrySet()) {
    synchronized void endTransaction(boolean success) throws ServiceException {        if (!mCurrentChange.isActive()) {            // would like to throw here, but it might cover another exception...            ZimbraLog.mailbox.warn("cannot end a transaction when not inside a transaction", new Exception());            return;        }        if (!mCurrentChange.endChange())            return;        Connection conn = mCurrentChange.conn;        ServiceException exception = null;        // update mailbox size and folder unread/message counts        if (success) {            try {                snapshotCounts();            } catch (ServiceException e) {                exception = e;                success = false;            }        }        // Failure case is very simple.  Just rollback the database and cache        // and return.  We haven't logged anything to the redo log for this        // transaction, so no redo cleanup is necessary.        if (!success) {            if (conn != null)                DbPool.quietRollback(conn);            rollbackCache(mCurrentChange);            if (exception != null)                throw exception;            return;        }        boolean needRedo = true;        if (mCurrentChange.octxt != null)            needRedo = mCurrentChange.octxt.needRedo();        RedoableOp redoRecorder = mCurrentChange.recorder;        IndexItem indexRedo = null;        Map<MailItem, Object> itemsToIndex = mCurrentChange.indexItems;        boolean indexingNeeded = !itemsToIndex.isEmpty() && !DebugConfig.disableIndexing;        // 1. Log the change redo record for main transaction.        //    If indexing is to be followed, log this entry        //    without requiring fsync, because logging for        //    indexing entry will do fsync, which will fsync        //    this entry at the same time.        if (redoRecorder != null && needRedo)            redoRecorder.log(!indexingNeeded);        boolean allGood = false;        try {            if (indexingNeeded) {                for (Map.Entry<MailItem, Object> entry : itemsToIndex.entrySet()) {                    MailItem item = entry.getKey();                    if (needRedo) {                        indexRedo = new IndexItem(mId, item.getId(), item.getType());                        indexRedo.start(getOperationTimestampMillis());                        indexRedo.setParentOp(redoRecorder);                    }                    // 2. Index the item before committing the main                    // transaction.  This allows us to fail the entire                    // transaction when indexing fails.  Write the change                    // record for indexing only after indexing actually                    // works.                    item.reindex(indexRedo, entry.getValue());                    // 3. Write the change redo record for indexing                    //    sub-transaction to guarantee that it appears in the                    //    redo log stream before the commit record for main                    //    transaction.  If main transaction commit record is                    //    written first and the server crashes before writing                    //    the indexing change record, we won't be able to                    //    re-execute indexing during crash recovery, and we will                    //    end up with an unindexed item.                    if (needRedo)                        indexRedo.log();                }            }            // 4. Commit the main transaction in database.            if (conn != null)                try {                    conn.commit();                } catch (Throwable t) {                    // Any exception during database commit is a disaster                    // because we don't know if the change is committed or                    // not.  Force the server to abort.  Next restart will                    // redo the operation to ensure the change is made and                    // committed.  (bug 2121)                    ZimbraLog.mailbox.fatal("Unable to commit database transaction.  Forcing server to abort.", t);                    Runtime.getRuntime().exit(1);                }                allGood = true;        } finally {            if (!allGood) {                // We will get here if indexing commit failed.                // (Database commit hasn't happened.)                // Write abort redo records to prevent the transactions from                // being redone during crash recovery.                // Write abort redo entries before doing database rollback.                // If we do rollback first and server crashes, crash                // recovery will try to redo the operation.                // Write abort redo record for indexing transaction before writing                // abort record for main.  This prevents indexing from                // being redone during crash recovery when main transaction                // was never committed.                if (needRedo) {                    if (indexRedo != null)                        indexRedo.abort();                    if (redoRecorder != null)                        redoRecorder.abort();                }                if (conn != null)                    DbPool.quietRollback(conn);                rollbackCache(mCurrentChange);            }        }        if (allGood) {            if (needRedo) {                // 5. Write commit record for main transaction.                //    By writing the commit record for main transaction before                //    calling MailItem.reindex(), we are guaranteed to see the                //    commit-main record in the redo stream before                //    commit-index record.  This order ensures that during                //    crash recovery the main transaction is redone before                //    indexing.  If the order were reversed, crash recovery                //    would attempt to index an item which hasn't been created                //    yet or would attempt to index the item with                //    pre-modification value.  The first case would result in                //    a redo error, and the second case would index the wrong                //    value.                if (redoRecorder != null)                    redoRecorder.commit();                    // 6. The commit redo record for indexing sub-transaction is                //    written in batch by another thread.  To avoid the batch                //    commit thread's writing commit-index before this thread's                //    writing commit-main (step 5 above), the index redo object                //    is initialized to block the commit attempt by default.                //    At this point we've written the commit-main record, so                //    unblock the commit on indexing.                if (indexRedo != null)                    indexRedo.allowCommit();            }            // 7. We are finally done with database and redo commits.            //    Cache update comes last.            commitCache(mCurrentChange);        }    }