logIn.setData(logOutputBuffer.getByteArray());
// use this scan to reconstitute operation to be undone
// when we see a CLR in the redo scan
StreamLogScan undoScan = null;
Loggable op = null;
long logEnd = 0; // we need to determine the log's true end
try
{
// scan the log forward in redo pass and go to the end
LogRecord record;
while((record =
redoScan.getNextRecord(logIn, null, 0))
!= null)
{
scanCount++;
long undoInstant = 0;
// last known good instant
instant = redoScan.getInstant();
// last known good log end
logEnd = redoScan.getLogRecordEnd();
// NOTE NOTE -- be very careful about the undoInstant, it is
// read off the input stream in this debug section.
// if we change the log format we will need to change the way
// the undo instant is gotten. Also, once it is read off, it
// should not be read from the stream any more
// NOTE NOTE
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY) ||
SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY))
SanityManager.DEBUG_SET(LogToFile.DBG_FLAG);
op = record.getLoggable();
tranId = record.getTransactionId();
if (record.isCLR())
{
// !!!!!!! this moves the file pointer
undoInstant = logIn.readLong();
SanityManager.DEBUG(
LogToFile.DBG_FLAG,
"scanned " + tranId + " : " + op +
" instant = " +
LogCounter.toDebugString(instant) +
" undoInstant : " +
LogCounter.toDebugString(undoInstant));
}
else
{
SanityManager.DEBUG(
LogToFile.DBG_FLAG,
"scanned " + tranId + " : " + op +
" instant = " +
LogCounter.toDebugString(instant)
+ " logEnd = " +
LogCounter.toDebugString(logEnd)
+ " logIn at " + logIn.getPosition()
+ " available " + logIn.available());
}
// we only want to dump the log, don't touch it
if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY))
continue;
}
}
// if the redo scan is between the undoLWM and redoLWM, we only
// need to redo begin and end tran. Everything else has
// already been flushed by checkpoint
if (redoLWM !=
LogCounter.INVALID_LOG_INSTANT && instant < redoLWM)
{
if (!(record.isFirst() ||
record.isComplete() ||
record.isPrepare()))
{
continue;
}
}
// get the transaction
tranId = record.getTransactionId();
// if this transaction is known to the transaction factory, make
// the recoveryTransaction assume its identitiy and properties
// otherwise, make it known to the transaction factory
if (!transFactory.findTransaction(tranId, recoveryTransaction))
{
// transaction not found
if (redoLWM != LogCounter.INVALID_LOG_INSTANT &&
instant < redoLWM &&
(record.isPrepare() || record.isComplete()))
{
// What is happening here is that a transaction that
// started before the undoLWM has commited by the time
// the checkpoint undoLWM was taken. Hence, we only
// see the tail end of its log record and its endXact
// record.
//
// NOTE:
// Since we didn't see its beginXact, we cannot do the
// endXact's doMe either. Also if the endXact, is
// actually just a prepare, we don't need to do
// anything as the transaction will commit or abort
// prior to point we are recovering to.
// If it is deemed necessary to do the endXact's doMe,
// then we should start the transaction right here.
// For now, just completely ignore this transaction
//
etranCount++;
continue;
}
if ((ttabInstant == LogCounter.INVALID_LOG_INSTANT) &&
!record.isFirst())
{
throw StandardException.newException(
SQLState.LOG_UNEXPECTED_RECOVERY_PROBLEM,
MessageService.getTextMessage(MessageId.LOG_RECORD_NOT_FIRST,tranId));
}
if (SanityManager.DEBUG)
{
// if we dumped the transaction table but see a non
// BeginXact record after the transaction table dump
// instant, error.
if (ttabInstant != LogCounter.INVALID_LOG_INSTANT)
{
if (instant > ttabInstant && !record.isFirst())
{
SanityManager.THROWASSERT(
"log record is Not first but transaction " +
"is not in transaction table (2) : " + tranId);
}
// If we dump the transaction table and the table
// does not have the transaction, and we see this
// beginXact before the ttab instant, we could have
// igored it because we "know" that we should see
// the endXact before the ttab instant also.
// Leave it in just in case.
}
}
btranCount++;
// the long transaction ID is embedded in the beginXact log
// record. The short ID is stored in the log record.
recoveryTransaction.setTransactionId(
record.getLoggable(), tranId);
}
else
{
// recoveryTransaction found
if ((ttabInstant == LogCounter.INVALID_LOG_INSTANT) &&
record.isFirst())
{
throw StandardException.newException(
SQLState.LOG_UNEXPECTED_RECOVERY_PROBLEM,
MessageService.getTextMessage(MessageId.LOG_RECORD_FIRST,
tranId));
}
if (SanityManager.DEBUG)
{
if (ttabInstant != LogCounter.INVALID_LOG_INSTANT &&
instant > ttabInstant &&
record.isFirst())
{
SanityManager.THROWASSERT(
"log record is first but transaction is " +
"already in transaction table (3): " + tranId);
}
if (record.isPrepare())
prepareCount++;
}
// if we have a transaction table dumped with the
// checkpoint log record, then during the redo scan we may
// see the beginXact of a transaction which is already in
// the transaction table, just ignore it if it is after the
// redoLWM but before the transaction table instant. We
// still need to redo any database changes but since the
// transaction is already recorded in the transaction
// table, ignore it.
//
if (record.isFirst())
{
btranCount++;
continue;
}
}
op = record.getLoggable();
if (SanityManager.DEBUG)
{
if (!record.isCLR())
{
if (logIn.available() < 4)
{
SanityManager.THROWASSERT(
"not enough bytes read in : " +
logIn.available() +
" for " + op + " instant " +
LogCounter.toDebugString(instant));
}
}
}
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(
!recoveryTransaction.handlesPostTerminationWork(),
"recovery transaction handles post termination work");
}
if (op.needsRedo(recoveryTransaction))
{
redoCount++;
if (record.isCLR())
{
clrCount++;
// the log operation is not complete, the operation to
// undo is stashed away at the undoInstant.
// Reconstitute that first.
if (SanityManager.DEBUG)
SanityManager.ASSERT(op instanceof Compensation);
// this value may be set by sanity xxxx
if (undoInstant == 0)
undoInstant = logIn.readLong();
if (undoScan == null)
{
undoScan = (StreamLogScan)
logFactory.openForwardsScan(
undoInstant,(LogInstant)null);
}
else
{
undoScan.resetPosition(new LogCounter(undoInstant));
}
// undoScan now positioned at the beginning of the log
// record was rolled back by this CLR.
// The scan is a forward one so getNextRecord will get
// the log record that needs to be rolled back.
// reuse the buffer in logIn and logIn since CLR
// has no optional data and has no use for them anymore
logIn.clearLimit();
LogRecord undoRecord =
undoScan.getNextRecord(logIn, null, 0);
Undoable undoOp = undoRecord.getUndoable();
if (SanityManager.DEBUG)
{
SanityManager.DEBUG(
LogToFile.DBG_FLAG,
"Redoing CLR: undoInstant = " +
LogCounter.toDebugString(undoInstant) +
" clrinstant = " +
LogCounter.toDebugString(instant));
SanityManager.ASSERT(
undoRecord.getTransactionId().equals(tranId));
SanityManager.ASSERT(undoOp != null);
}
((Compensation)op).setUndoOp(undoOp);
}
// at this point, logIn points to the optional
// data of the loggable that is to be redone or to be
// rolled back
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
SanityManager.DEBUG(
LogToFile.DBG_FLAG,
"redoing " + op +
" instant = " +
LogCounter.toDebugString(instant));
}
}
int dataLength = logIn.readInt();
logIn.setLimit(dataLength);
// even though the log has already been written, we need to
// tie the page to the log stream so that if redo failed
// for some reasons, the log factory's corruption will stop
// the corrupt page from flushing to disk.
op.doMe(
recoveryTransaction,
new LogCounter(instant), logIn);
op.releaseResource(recoveryTransaction);
op = null;
}
// RESOLVE: to speed up undo, may want to update the
// LastLogInstant in the transaction table.
// Right now, undo always start from the end of the log.
// one last thing, if this is the last log record of the
// transaction, then commit the transaction and clean up
//
// 'commit' even though the transaction maybe a rollback
// because we already did all the rollback work when redoing
// the CLRs. Commit will only flush the log if this session
// has written any transaction, so in this case, it is a noop.
if (record.isComplete())
{
etranCount++;
if (SanityManager.DEBUG)
SanityManager.ASSERT(
!recoveryTransaction.handlesPostTerminationWork(),
"recovery xact handles post termination work");
recoveryTransaction.commit();
}
} // while redoScan.getNextRecord() != null
// If the scan ended in an empty file, update logEnd to reflect that
// in order to avoid to continue logging to an older file
long end = redoScan.getLogRecordEnd();
if (end != LogCounter.INVALID_LOG_INSTANT
&& (LogCounter.getLogFileNumber(logEnd)
< LogCounter.getLogFileNumber(end))) {
logEnd = end;
}
}
catch (StandardException se)
{
throw StandardException.newException(
SQLState.LOG_REDO_FAILED, se, op);
}
finally
{
// close all the io streams
redoScan.close();
redoScan = null;
if (undoScan != null)
{
undoScan.close();
undoScan = null;
}
if (op != null)
op.releaseResource(recoveryTransaction);
}
if (SanityManager.DEBUG)
{