/* * AtPrepare_Notify * * This is called at the prepare phase of a two-phase * transaction. Save the state for possible commit later. */ void AtPrepare_Notify(void) { ListCell *p; /* It's not sensible to have any pending LISTEN/UNLISTEN actions */ if (pendingActions) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot PREPARE a transaction that has executed LISTEN or UNLISTEN"))); /* We can deal with pending NOTIFY though */ foreach(p, pendingNotifies) { const char *relname = (const char *) lfirst(p); RegisterTwoPhaseRecord(TWOPHASE_RM_NOTIFY_ID, 0, relname, strlen(relname) + 1); } /* * We can clear the state immediately, rather than needing a separate * PostPrepare call, because if the transaction fails we'd just discard * the state anyway. */ ClearPendingActionsAndNotifies(); }
/* * AtPrepare_Inval * Save the inval lists state at 2PC transaction prepare. * * In this phase we just generate 2PC records for all the pending invalidation * work. */ void AtPrepare_Inval(void) { /* Must be at top of stack */ Assert(transInvalInfo != NULL && transInvalInfo->parent == NULL); /* * Relcache init file invalidation requires processing both before and * after we send the SI messages. */ if (transInvalInfo->RelcacheInitFileInval) RegisterTwoPhaseRecord(TWOPHASE_RM_INVAL_ID, TWOPHASE_INFO_FILE_BEFORE, NULL, 0); AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs, &transInvalInfo->CurrentCmdInvalidMsgs); ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs, PersistInvalidationMessage); if (transInvalInfo->RelcacheInitFileInval) RegisterTwoPhaseRecord(TWOPHASE_RM_INVAL_ID, TWOPHASE_INFO_FILE_AFTER, NULL, 0); }
/* *-------------------------------------------------------------- * AtPrepare_Notify * * This is called at the prepare phase of a two-phase * transaction. Save the state for possible commit later. *-------------------------------------------------------------- */ void AtPrepare_Notify(void) { ListCell *p; foreach(p, pendingNotifies) { const char *relname = (const char *) lfirst(p); RegisterTwoPhaseRecord(TWOPHASE_RM_NOTIFY_ID, 0, relname, strlen(relname) + 1); } /* * We can clear the state immediately, rather than needing a separate * PostPrepare call, because if the transaction fails we'd just discard * the state anyway. */ ClearPendingNotifies(); }
/* * Finish preparing state file. * * Calculates CRC and writes state file to WAL and in pg_twophase directory. */ void EndPrepare(GlobalTransaction gxact) { TransactionId xid = gxact->proc.xid; TwoPhaseFileHeader *hdr; char path[MAXPGPATH]; XLogRecData *record; pg_crc32 statefile_crc; pg_crc32 bogus_crc; int fd; /* Add the end sentinel to the list of 2PC records */ RegisterTwoPhaseRecord(TWOPHASE_RM_END_ID, 0, NULL, 0); /* Go back and fill in total_len in the file header record */ hdr = (TwoPhaseFileHeader *) records.head->data; Assert(hdr->magic == TWOPHASE_MAGIC); hdr->total_len = records.total_len + sizeof(pg_crc32); /* * If the file size exceeds MaxAllocSize, we won't be able to read it in * ReadTwoPhaseFile. Check for that now, rather than fail at commit time. */ if (hdr->total_len > MaxAllocSize) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("two-phase state file maximum length exceeded"))); /* * Create the 2PC state file. * * Note: because we use BasicOpenFile(), we are responsible for ensuring * the FD gets closed in any error exit path. Once we get into the * critical section, though, it doesn't matter since any failure causes * PANIC anyway. */ TwoPhaseFilePath(path, xid); fd = BasicOpenFile(path, O_CREAT | O_EXCL | O_WRONLY | PG_BINARY, S_IRUSR | S_IWUSR); if (fd < 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not create two-phase state file \"%s\": %m", path))); /* Write data to file, and calculate CRC as we pass over it */ INIT_CRC32(statefile_crc); for (record = records.head; record != NULL; record = record->next) { COMP_CRC32(statefile_crc, record->data, record->len); if ((write(fd, record->data, record->len)) != record->len) { close(fd); ereport(ERROR, (errcode_for_file_access(), errmsg("could not write two-phase state file: %m"))); } } FIN_CRC32(statefile_crc); /* * Write a deliberately bogus CRC to the state file; this is just paranoia * to catch the case where four more bytes will run us out of disk space. */ bogus_crc = ~statefile_crc; if ((write(fd, &bogus_crc, sizeof(pg_crc32))) != sizeof(pg_crc32)) { close(fd); ereport(ERROR, (errcode_for_file_access(), errmsg("could not write two-phase state file: %m"))); } /* Back up to prepare for rewriting the CRC */ if (lseek(fd, -((off_t) sizeof(pg_crc32)), SEEK_CUR) < 0) { close(fd); ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in two-phase state file: %m"))); } /* * The state file isn't valid yet, because we haven't written the correct * CRC yet. Before we do that, insert entry in WAL and flush it to disk. * * Between the time we have written the WAL entry and the time we write * out the correct state file CRC, we have an inconsistency: the xact is * prepared according to WAL but not according to our on-disk state. We * use a critical section to force a PANIC if we are unable to complete * the write --- then, WAL replay should repair the inconsistency. The * odds of a PANIC actually occurring should be very tiny given that we * were able to write the bogus CRC above. * * We have to set inCommit here, too; otherwise a checkpoint starting * immediately after the WAL record is inserted could complete without * fsync'ing our state file. (This is essentially the same kind of race * condition as the COMMIT-to-clog-write case that RecordTransactionCommit * uses inCommit for; see notes there.) * * We save the PREPARE record's location in the gxact for later use by * CheckPointTwoPhase. */ START_CRIT_SECTION(); MyProc->inCommit = true; gxact->prepare_lsn = XLogInsert(RM_XACT_ID, XLOG_XACT_PREPARE, records.head); XLogFlush(gxact->prepare_lsn); /* If we crash now, we have prepared: WAL replay will fix things */ /* write correct CRC and close file */ if ((write(fd, &statefile_crc, sizeof(pg_crc32))) != sizeof(pg_crc32)) { close(fd); ereport(ERROR, (errcode_for_file_access(), errmsg("could not write two-phase state file: %m"))); } if (close(fd) != 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not close two-phase state file: %m"))); /* * Mark the prepared transaction as valid. As soon as xact.c marks MyProc * as not running our XID (which it will do immediately after this * function returns), others can commit/rollback the xact. * * NB: a side effect of this is to make a dummy ProcArray entry for the * prepared XID. This must happen before we clear the XID from MyProc, * else there is a window where the XID is not running according to * TransactionIdIsInProgress, and onlookers would be entitled to assume * the xact crashed. Instead we have a window where the same XID appears * twice in ProcArray, which is OK. */ MarkAsPrepared(gxact); /* * Now we can mark ourselves as out of the commit critical section: a * checkpoint starting after this will certainly see the gxact as a * candidate for fsyncing. */ MyProc->inCommit = false; END_CRIT_SECTION(); records.tail = records.head = NULL; }
/* * PersistInvalidationMessage * Write an invalidation message to the 2PC state file. */ static void PersistInvalidationMessage(SharedInvalidationMessage *msg) { RegisterTwoPhaseRecord(TWOPHASE_RM_INVAL_ID, TWOPHASE_INFO_MSG, msg, sizeof(SharedInvalidationMessage)); }