2004-06-24 21:10:31 +00:00
|
|
|
/**
|
|
|
|
|
|
|
|
Replacement for recovery.c
|
|
|
|
|
|
|
|
A lot of refactoring has been done to simplify the contents of recovery.c
|
|
|
|
|
|
|
|
Hopefully, this file will be nice and clean. :)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2004-07-06 01:22:18 +00:00
|
|
|
#include <config.h>
|
|
|
|
#include <lladd/common.h>
|
|
|
|
#include <lladd/recovery.h>
|
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
#include <pbl/pbl.h>
|
|
|
|
#include "linkedlist.h"
|
2004-07-14 21:25:59 +00:00
|
|
|
#include "logger/logHandle.h"
|
2004-06-24 21:10:31 +00:00
|
|
|
#include <lladd/bufferManager.h>
|
2005-02-10 21:56:32 +00:00
|
|
|
#include <lladd/lockManager.h>
|
2004-06-24 21:10:31 +00:00
|
|
|
|
2007-03-03 01:52:03 +00:00
|
|
|
#include "page.h" // Needed for pageReadLSN.
|
2004-07-23 20:21:44 +00:00
|
|
|
|
|
|
|
|
2004-07-06 01:22:18 +00:00
|
|
|
#include <lladd/transactional.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2004-06-24 21:10:31 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
2004-07-06 01:22:18 +00:00
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
/** @todo This include is an artifact of our lack of infrastructure to support log iterator guards. */
|
|
|
|
#include <lladd/operations/prepare.h>
|
|
|
|
|
|
|
|
static pblHashTable_t * transactionLSN;
|
2006-10-28 03:31:27 +00:00
|
|
|
static LinkedList * rollbackLSNs = NULL;
|
2006-08-08 01:41:45 +00:00
|
|
|
/** @todo There is no real reason to have this mutex (which prevents
|
|
|
|
concurrent aborts, except that we need to protect rollbackLSNs's
|
|
|
|
from concurrent modifications. */
|
|
|
|
static pthread_mutex_t rollback_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
/**
|
|
|
|
Determines which transactions committed, and which need to be redone.
|
|
|
|
|
|
|
|
In the original version, this function also:
|
|
|
|
- Determined the point in the log at which to start the Redo pass.
|
|
|
|
- Calculated a list of all dirty pages.
|
|
|
|
|
|
|
|
It no longer does either of these things:
|
|
|
|
- A checkpointing algorithm could figure out where the redo pass
|
|
|
|
should begin. (It would then truncate the log at that point.) This
|
|
|
|
function could be called before analysis if efficiency is a concern.
|
|
|
|
- We were using the list of dirty pages as an optimization to prevent
|
|
|
|
the pages from being read later during recovery. Since this function
|
|
|
|
no longer reads the pages in, there's no longer any reason to build
|
|
|
|
the list of dirty pages.
|
|
|
|
|
|
|
|
*/
|
|
|
|
static void Analysis () {
|
|
|
|
|
2006-04-11 02:20:21 +00:00
|
|
|
const LogEntry * e;
|
2004-06-24 21:10:31 +00:00
|
|
|
|
|
|
|
LogHandle lh = getLogHandle();
|
|
|
|
|
|
|
|
/** After recovery, we need to know what the highest XID in the
|
|
|
|
log was so that we don't accidentally reuse XID's. This keeps
|
|
|
|
track of that value. */
|
|
|
|
int highestXid = 0;
|
|
|
|
|
|
|
|
/** @todo loadCheckPoint() - Jump forward in the log to the last
|
|
|
|
checkpoint. (Maybe getLogHandle should do this automatically,
|
|
|
|
since the log will be truncated on checkpoint anyway.) */
|
|
|
|
|
|
|
|
while((e = nextInLog(&lh))) {
|
|
|
|
|
|
|
|
lsn_t * xactLSN = (lsn_t*)pblHtLookup(transactionLSN, &(e->xid), sizeof(int));
|
|
|
|
/* recordid rid = e->contents.update.rid; */
|
|
|
|
|
|
|
|
if(highestXid < e->xid) {
|
|
|
|
highestXid = e->xid;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Track LSN's in two data structures:
|
|
|
|
- map: xid -> max LSN
|
|
|
|
- sorted list of maxLSN's
|
|
|
|
*/
|
|
|
|
|
|
|
|
if(xactLSN == NULL) {
|
|
|
|
xactLSN = malloc(sizeof(lsn_t));
|
|
|
|
pblHtInsert(transactionLSN, &(e->xid), sizeof(int), xactLSN);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* We've seen this xact before, and must have put a value in
|
|
|
|
rollbackLSNs for it. That value is now stale, so remove
|
|
|
|
it. */
|
|
|
|
|
|
|
|
DEBUG("Removing %ld\n", *xactLSN);
|
|
|
|
removeVal(&rollbackLSNs, *xactLSN);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now, rollbackLSNs certainly does not contain an LSN for this
|
|
|
|
transaction, and *xactLSN points to a value in the hash, so
|
|
|
|
writing to it updates the hash. This doesn't update the
|
|
|
|
rollbackLSN data structure, so it doesn't hurt to update this
|
|
|
|
value for all log entries. */
|
|
|
|
|
|
|
|
*xactLSN = e->LSN;
|
|
|
|
|
|
|
|
switch(e->type) {
|
|
|
|
case XCOMMIT:
|
|
|
|
/* We've removed this XACT's last LSN from the list of LSN's to
|
|
|
|
be rolled back, so we're done. */
|
|
|
|
break;
|
|
|
|
case XEND:
|
|
|
|
/*
|
|
|
|
XEND means this transaction reached stable storage.
|
|
|
|
Therefore, we can skip redoing any of its operations. (The
|
|
|
|
timestamps on each page guarantee that the redo phase will
|
|
|
|
not overwrite this transaction's work with stale data.)
|
2006-09-27 20:28:44 +00:00
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
The redo phase checks for a transaction's presence in
|
2004-06-28 22:48:02 +00:00
|
|
|
transactionLSN before redoing its actions. Therefore, if we
|
|
|
|
remove this transaction from the hash, it will not be redone.
|
2004-06-24 21:10:31 +00:00
|
|
|
*/
|
|
|
|
pblHtRemove(transactionLSN, &(e->xid), sizeof(int));
|
|
|
|
break;
|
|
|
|
case UPDATELOG:
|
2007-03-30 09:16:21 +00:00
|
|
|
// XXX we should treat CLR's like REDO's, but things don't work
|
|
|
|
// that way yet.
|
2004-06-24 21:10:31 +00:00
|
|
|
case CLRLOG:
|
|
|
|
/*
|
|
|
|
If the last record we see for a transaction is an update or clr,
|
|
|
|
then the transaction must not have committed, so it must need
|
|
|
|
to be rolled back.
|
|
|
|
|
|
|
|
Add it to the appropriate list
|
|
|
|
|
|
|
|
*/
|
|
|
|
DEBUG("Adding %ld\n", e->LSN);
|
|
|
|
|
|
|
|
addSortedVal(&rollbackLSNs, e->LSN);
|
|
|
|
break;
|
|
|
|
case XABORT:
|
2007-03-30 09:16:21 +00:00
|
|
|
/* If the last record we see for a transaction is an abort, then
|
|
|
|
the transaction didn't commit, and must be rolled back.
|
|
|
|
*/
|
|
|
|
DEBUG("Adding %ld\n", e->LSN);
|
|
|
|
addSortedVal(&rollbackLSNs, e->LSN);
|
2006-10-04 04:41:19 +00:00
|
|
|
break;
|
|
|
|
case INTERNALLOG:
|
|
|
|
/* Created by the logger, just ignore it. */
|
2007-03-30 09:16:21 +00:00
|
|
|
// Make sure the log entry doesn't interfere with real xacts.
|
|
|
|
assert(e->xid == INVALID_XID);
|
2006-10-04 04:41:19 +00:00
|
|
|
break;
|
2004-06-24 21:10:31 +00:00
|
|
|
default:
|
2006-10-04 04:41:19 +00:00
|
|
|
abort();
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|
2006-04-11 02:20:21 +00:00
|
|
|
FreeLogEntry(e);
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|
|
|
|
TsetXIDCount(highestXid);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Redo() {
|
|
|
|
LogHandle lh = getLogHandle();
|
2006-04-11 02:20:21 +00:00
|
|
|
const LogEntry * e;
|
2004-06-24 21:10:31 +00:00
|
|
|
|
|
|
|
while((e = nextInLog(&lh))) {
|
2004-06-28 22:48:02 +00:00
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
/* Check to see if this log entry is part of a transaction that needs to be redone. */
|
|
|
|
if(pblHtLookup(transactionLSN, &(e->xid), sizeof(int)) != NULL) {
|
2007-03-30 09:16:21 +00:00
|
|
|
// Check to see if this entry's action needs to be redone
|
|
|
|
switch(e->type) {
|
|
|
|
case UPDATELOG:
|
|
|
|
case CLRLOG:
|
|
|
|
{
|
|
|
|
// redoUpdate checks the page that contains e->rid, so we
|
|
|
|
// don't need to check to see if the page is newer than this
|
|
|
|
// log entry.
|
|
|
|
redoUpdate(e);
|
|
|
|
FreeLogEntry(e);
|
|
|
|
} break;
|
|
|
|
case DEFERLOG:
|
|
|
|
{
|
|
|
|
//XXX deferred_push(e);
|
|
|
|
} break;
|
|
|
|
case XCOMMIT:
|
|
|
|
{
|
|
|
|
if(globalLockManager.commit)
|
|
|
|
globalLockManager.commit(e->xid);
|
|
|
|
FreeLogEntry(e);
|
|
|
|
} break;
|
|
|
|
case XABORT:
|
|
|
|
{
|
|
|
|
// wait until undo is complete before informing the lock manager
|
|
|
|
FreeLogEntry(e);
|
|
|
|
} break;
|
|
|
|
case INTERNALLOG:
|
|
|
|
{
|
|
|
|
FreeLogEntry(e);
|
|
|
|
} break;
|
|
|
|
default:
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|
|
|
|
}
|
2007-03-30 09:16:21 +00:00
|
|
|
/**
|
|
|
|
XXX
|
|
|
|
@todo CLR handling seems to be broken for logical operations!
|
|
|
|
*/
|
2004-06-24 21:10:31 +00:00
|
|
|
static void Undo(int recovery) {
|
|
|
|
LogHandle lh;
|
|
|
|
void * prepare_guard_state;
|
|
|
|
|
|
|
|
while(rollbackLSNs != NULL) {
|
2006-04-11 02:20:21 +00:00
|
|
|
const LogEntry * e;
|
2004-06-24 21:10:31 +00:00
|
|
|
lsn_t rollback = popMaxVal(&rollbackLSNs);
|
|
|
|
|
2004-10-02 07:29:34 +00:00
|
|
|
prepare_guard_state = getPrepareGuardState();
|
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
DEBUG("Undoing LSN %ld\n", (long int)rollback);
|
|
|
|
if(recovery) {
|
|
|
|
/** @todo shouldn't be hardcoded here! */
|
|
|
|
lh = getGuardedHandle(rollback, &prepareGuard, prepare_guard_state);
|
|
|
|
} else {
|
|
|
|
/** @todo probably want guards that are run during normal operation. */
|
|
|
|
lh = getLSNHandle(rollback);
|
|
|
|
}
|
2004-07-04 00:46:49 +00:00
|
|
|
|
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
/* printf("e->prev_offset: %ld\n", e->prevLSN);
|
|
|
|
printf("prev_offset: %ld\n", lh.prev_offset); */
|
2005-02-10 21:56:32 +00:00
|
|
|
int thisXid = -1;
|
2004-06-24 21:10:31 +00:00
|
|
|
while((e = previousInTransaction(&lh))) {
|
2005-02-10 21:56:32 +00:00
|
|
|
thisXid = e->xid;
|
2004-06-24 21:10:31 +00:00
|
|
|
lsn_t this_lsn, clr_lsn;
|
|
|
|
/* printf("."); fflush(NULL); */
|
|
|
|
switch(e->type) {
|
|
|
|
case UPDATELOG:
|
2004-07-23 20:21:44 +00:00
|
|
|
{
|
2004-10-18 18:24:54 +00:00
|
|
|
/* Need write lock for undo.. (Why??) */
|
2005-01-14 10:08:10 +00:00
|
|
|
if(e->contents.update.rid.size != -1) {
|
|
|
|
|
2006-04-14 03:45:26 +00:00
|
|
|
Page * p = loadPage(thisXid, e->contents.update.rid.page);
|
2006-09-27 20:28:44 +00:00
|
|
|
this_lsn= pageReadLSN(p);
|
2005-01-14 10:08:10 +00:00
|
|
|
|
|
|
|
/* Sanity check. If this fails, something is wrong with the
|
|
|
|
redo phase or normal operation. */
|
|
|
|
assert(e->LSN <= this_lsn);
|
|
|
|
|
|
|
|
/* Need to log a clr here. */
|
|
|
|
|
2006-04-06 03:22:57 +00:00
|
|
|
clr_lsn = LogCLR(e->xid, e->LSN, e->contents.update.rid, e->prevLSN);
|
2005-01-14 10:08:10 +00:00
|
|
|
|
|
|
|
/* Undo update is a no-op if the page does not reflect this
|
|
|
|
update, but it will write the new clr_lsn if necessary. */
|
|
|
|
|
|
|
|
undoUpdate(e, p, clr_lsn);
|
2006-09-27 20:28:44 +00:00
|
|
|
|
2005-01-14 10:08:10 +00:00
|
|
|
releasePage(p);
|
|
|
|
} else {
|
|
|
|
// The log entry is not associated with a particular page.
|
|
|
|
// (Therefore, it must be an idempotent logical log entry.)
|
2007-03-30 09:16:21 +00:00
|
|
|
clr_lsn = LogCLR(e->xid, e->LSN, e->contents.update.rid,
|
|
|
|
e->prevLSN);
|
2005-01-14 10:08:10 +00:00
|
|
|
undoUpdate(e, NULL, clr_lsn);
|
|
|
|
}
|
2004-07-23 20:21:44 +00:00
|
|
|
break;
|
|
|
|
}
|
2007-03-30 09:16:21 +00:00
|
|
|
case DEFERLOG:
|
|
|
|
// The transaction is aborting, so it never committed. Therefore
|
|
|
|
// actions deferred to commit have never been applied; ignore this
|
|
|
|
// log entry.
|
|
|
|
break;
|
2004-06-24 21:10:31 +00:00
|
|
|
case CLRLOG:
|
2004-06-28 22:48:02 +00:00
|
|
|
/* Don't need to do anything special to handle CLR's.
|
|
|
|
Iterator will correctly jump to clr's previous undo record. */
|
2004-06-24 21:10:31 +00:00
|
|
|
break;
|
2004-06-28 21:10:10 +00:00
|
|
|
case XABORT:
|
2004-06-28 22:48:02 +00:00
|
|
|
/* Since XABORT is a no-op, we can silentlt ignore it. (XABORT
|
|
|
|
records may be passed in by undoTrans.)*/
|
2004-06-28 21:10:10 +00:00
|
|
|
break;
|
2004-06-24 21:10:31 +00:00
|
|
|
default:
|
2007-03-30 09:16:21 +00:00
|
|
|
print
|
|
|
|
("Unknown log type to undo (TYPE=%d,XID= %d,LSN=%lld), skipping...\n",
|
|
|
|
e->type, e->xid, e->LSN);
|
2004-06-24 21:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
2006-04-11 02:20:21 +00:00
|
|
|
FreeLogEntry(e);
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|
2005-02-10 21:56:32 +00:00
|
|
|
int transactionWasPrepared = prepareAction(prepare_guard_state);
|
2004-10-02 07:29:34 +00:00
|
|
|
free(prepare_guard_state);
|
2005-02-10 21:56:32 +00:00
|
|
|
if(!transactionWasPrepared && globalLockManager.abort) {
|
|
|
|
globalLockManager.abort(thisXid);
|
|
|
|
}
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InitiateRecovery() {
|
|
|
|
|
|
|
|
transactionLSN = pblHtCreate();
|
|
|
|
DEBUG("Analysis started\n");
|
|
|
|
Analysis();
|
|
|
|
DEBUG("Redo started\n");
|
|
|
|
Redo();
|
|
|
|
DEBUG("Undo started\n");
|
|
|
|
Undo(1);
|
|
|
|
DEBUG("Recovery complete.\n");
|
2004-06-28 22:48:02 +00:00
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
pblHtDelete(transactionLSN);
|
|
|
|
|
2006-10-28 03:31:27 +00:00
|
|
|
destroyList(&rollbackLSNs);
|
|
|
|
assert(rollbackLSNs==0);
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void undoTrans(TransactionLog transaction) {
|
2006-08-08 01:41:45 +00:00
|
|
|
|
|
|
|
pthread_mutex_lock(&rollback_mutex);
|
|
|
|
assert(!rollbackLSNs);
|
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
if(transaction.prevLSN > 0) {
|
2004-06-28 22:48:02 +00:00
|
|
|
DEBUG("scheduling lsn %ld for undo.\n", transaction.prevLSN);
|
2004-06-24 21:10:31 +00:00
|
|
|
addSortedVal(&rollbackLSNs, transaction.prevLSN);
|
|
|
|
} else {
|
2004-06-28 22:48:02 +00:00
|
|
|
/* Nothing to undo. (Happens for read-only xacts.) */
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Undo(0);
|
2006-08-08 01:41:45 +00:00
|
|
|
if(rollbackLSNs) {
|
2006-10-28 03:31:27 +00:00
|
|
|
destroyList(&rollbackLSNs);
|
2006-08-08 01:41:45 +00:00
|
|
|
}
|
2006-10-28 03:31:27 +00:00
|
|
|
assert(rollbackLSNs == 0);
|
2006-08-08 01:41:45 +00:00
|
|
|
pthread_mutex_unlock(&rollback_mutex);
|
|
|
|
|
2004-06-24 21:10:31 +00:00
|
|
|
}
|