/* * MUST BE CALLED ONLY ON RECOVERY. * * Check if exists valid (inserted by not aborted xaction) heap tuple * for given item pointer */ bool XLogIsValidTuple(RelFileNode hnode, ItemPointer iptr) { Relation reln; Buffer buffer; Page page; ItemId lp; HeapTupleHeader htup; reln = XLogOpenRelation(false, RM_HEAP_ID, hnode); if (!RelationIsValid(reln)) return (false); buffer = ReadBuffer(reln, ItemPointerGetBlockNumber(iptr)); if (!BufferIsValid(buffer)) return (false); LockBuffer(buffer, BUFFER_LOCK_SHARE); page = (Page) BufferGetPage(buffer); if (PageIsNew((PageHeader) page) || ItemPointerGetOffsetNumber(iptr) > PageGetMaxOffsetNumber(page)) { UnlockAndReleaseBuffer(buffer); return (false); } if (PageGetSUI(page) != ThisStartUpID) { Assert(PageGetSUI(page) < ThisStartUpID); UnlockAndReleaseBuffer(buffer); return (true); } lp = PageGetItemId(page, ItemPointerGetOffsetNumber(iptr)); if (!ItemIdIsUsed(lp) || ItemIdDeleted(lp)) { UnlockAndReleaseBuffer(buffer); return (false); } htup = (HeapTupleHeader) PageGetItem(page, lp); /* MUST CHECK WASN'T TUPLE INSERTED IN PREV STARTUP */ if (!(htup->t_infomask & HEAP_XMIN_COMMITTED)) { if (htup->t_infomask & HEAP_XMIN_INVALID || (htup->t_infomask & HEAP_MOVED_IN && TransactionIdDidAbort(HeapTupleHeaderGetXvac(htup))) || TransactionIdDidAbort(HeapTupleHeaderGetXmin(htup))) { UnlockAndReleaseBuffer(buffer); return (false); } } UnlockAndReleaseBuffer(buffer); return (true); }
void StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid) { xl_standby_lock *newlock; LOCKTAG locktag; /* Already processed? */ if (TransactionIdDidCommit(xid) || TransactionIdDidAbort(xid)) return; elog(trace_recovery(DEBUG4), "adding recovery lock: db %u rel %u", dbOid, relOid); /* dbOid is InvalidOid when we are locking a shared relation. */ Assert(OidIsValid(relOid)); newlock = palloc(sizeof(xl_standby_lock)); newlock->xid = xid; newlock->dbOid = dbOid; newlock->relOid = relOid; RecoveryLockList = lappend(RecoveryLockList, newlock); /* * Attempt to acquire the lock as requested, if not resolve conflict */ SET_LOCKTAG_RELATION(locktag, newlock->dbOid, newlock->relOid); if (LockAcquireExtended(&locktag, AccessExclusiveLock, true, true, false) == LOCKACQUIRE_NOT_AVAIL) ResolveRecoveryConflictWithLock(newlock->dbOid, newlock->relOid); }
/* * XactLockTableWait * * Wait for the specified transaction to commit or abort. */ void XactLockTableWait(TransactionId xid) { LOCKTAG tag; TransactionId myxid = GetCurrentTransactionId(); Assert(!TransactionIdEquals(xid, myxid)); MemSet(&tag, 0, sizeof(tag)); tag.relId = XactLockTableId; tag.dbId = InvalidOid; tag.objId.xid = xid; if (!LockAcquire(LockTableId, &tag, myxid, ShareLock, false)) elog(ERROR, "LockAcquire failed"); LockRelease(LockTableId, &tag, myxid, ShareLock); /* * Transaction was committed/aborted/crashed - we have to update * pg_clog if transaction is still marked as running. */ if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid)) TransactionIdAbort(xid); }
/* * TransactionIdDidAbort * True iff transaction associated with the identifier did abort. * * Note: * Assumes transaction identifier is valid. */ bool /* true if given transaction aborted */ TransactionIdDidAbort(TransactionId transactionId) { XidStatus xidstatus; xidstatus = TransactionLogFetch(transactionId); /* * If it's marked aborted, it's aborted. */ if (xidstatus == TRANSACTION_STATUS_ABORTED) return true; /* * If it's marked subcommitted, we have to check the parent recursively. * However, if it's older than TransactionXmin, we can't look at * pg_subtrans; instead assume that the parent crashed without cleaning up * its children. */ if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED) { TransactionId parentXid; if (TransactionIdPrecedes(transactionId, TransactionXmin)) return true; parentXid = SubTransGetParent(transactionId); if (!TransactionIdIsValid(parentXid)) { /* see notes in TransactionIdDidCommit */ elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId); return true; } return TransactionIdDidAbort(parentXid); } /* * It's not aborted. */ return false; }
/* * RecoverPreparedTransactions * * Scan the pg_twophase directory and reload shared-memory state for each * prepared transaction (reacquire locks, etc). This is run during database * startup. */ void RecoverPreparedTransactions(void) { char dir[MAXPGPATH]; DIR *cldir; struct dirent *clde; snprintf(dir, MAXPGPATH, "%s", TWOPHASE_DIR); cldir = AllocateDir(dir); while ((clde = ReadDir(cldir, dir)) != NULL) { if (strlen(clde->d_name) == 8 && strspn(clde->d_name, "0123456789ABCDEF") == 8) { TransactionId xid; char *buf; char *bufptr; TwoPhaseFileHeader *hdr; TransactionId *subxids; GlobalTransaction gxact; int i; xid = (TransactionId) strtoul(clde->d_name, NULL, 16); /* Already processed? */ if (TransactionIdDidCommit(xid) || TransactionIdDidAbort(xid)) { ereport(WARNING, (errmsg("removing stale two-phase state file \"%s\"", clde->d_name))); RemoveTwoPhaseFile(xid, true); continue; } /* Read and validate file */ buf = ReadTwoPhaseFile(xid); if (buf == NULL) { ereport(WARNING, (errmsg("removing corrupt two-phase state file \"%s\"", clde->d_name))); RemoveTwoPhaseFile(xid, true); continue; } ereport(LOG, (errmsg("recovering prepared transaction %u", xid))); /* Deconstruct header */ hdr = (TwoPhaseFileHeader *) buf; Assert(TransactionIdEquals(hdr->xid, xid)); bufptr = buf + MAXALIGN(sizeof(TwoPhaseFileHeader)); subxids = (TransactionId *) bufptr; bufptr += MAXALIGN(hdr->nsubxacts * sizeof(TransactionId)); bufptr += MAXALIGN(hdr->ncommitrels * sizeof(RelFileNode)); bufptr += MAXALIGN(hdr->nabortrels * sizeof(RelFileNode)); /* * Reconstruct subtrans state for the transaction --- needed * because pg_subtrans is not preserved over a restart. Note that * we are linking all the subtransactions directly to the * top-level XID; there may originally have been a more complex * hierarchy, but there's no need to restore that exactly. */ for (i = 0; i < hdr->nsubxacts; i++) SubTransSetParent(subxids[i], xid); /* * Recreate its GXACT and dummy PGPROC * * Note: since we don't have the PREPARE record's WAL location at * hand, we leave prepare_lsn zeroes. This means the GXACT will * be fsync'd on every future checkpoint. We assume this * situation is infrequent enough that the performance cost is * negligible (especially since we know the state file has already * been fsynced). */ gxact = MarkAsPreparing(xid, hdr->gid, hdr->prepared_at, hdr->owner, hdr->database); GXactLoadSubxactData(gxact, hdr->nsubxacts, subxids); MarkAsPrepared(gxact); /* * Recover other state (notably locks) using resource managers */ ProcessRecords(bufptr, xid, twophase_recover_callbacks); pfree(buf); } } FreeDir(cldir); }