void ResolveRecoveryConflictWithDatabase(Oid dbid) { /* * We don't do ResolveRecoveryConflictWithVirtualXIDs() here since that * only waits for transactions and completely idle sessions would block * us. This is rare enough that we do this as simply as possible: no wait, * just force them off immediately. * * No locking is required here because we already acquired * AccessExclusiveLock. Anybody trying to connect while we do this will * block during InitPostgres() and then disconnect when they see the * database has been removed. */ while (CountDBBackends(dbid) > 0) { CancelDBBackends(dbid, PROCSIG_RECOVERY_CONFLICT_DATABASE, true); /* * Wait awhile for them to die so that we avoid flooding an * unresponsive backend when system is heavily loaded. */ pg_usleep(10000); } }
/* * CheckMyDatabase -- fetch information from the pg_database entry for our DB */ static void CheckMyDatabase(const char *name, bool am_superuser) { HeapTuple tup; Form_pg_database dbform; char *collate; char *ctype; /* Fetch our pg_database row normally, via syscache */ tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); dbform = (Form_pg_database) GETSTRUCT(tup); /* This recheck is strictly paranoia */ if (strcmp(name, NameStr(dbform->datname)) != 0) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" has disappeared from pg_database", name), errdetail("Database OID %u now seems to belong to \"%s\".", MyDatabaseId, NameStr(dbform->datname)))); /* * Check permissions to connect to the database. * * These checks are not enforced when in standalone mode, so that there is * a way to recover from disabling all access to all databases, for * example "UPDATE pg_database SET datallowconn = false;". * * We do not enforce them for autovacuum worker processes either. */ if (IsUnderPostmaster && !IsAutoVacuumWorkerProcess()) { /* * Check that the database is currently allowing connections. */ if (!dbform->datallowconn) ereport(FATAL, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("database \"%s\" is not currently accepting connections", name))); /* * Check privilege to connect to the database. (The am_superuser test * is redundant, but since we have the flag, might as well check it * and save a few cycles.) */ if (!am_superuser && pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CONNECT) != ACLCHECK_OK) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied for database \"%s\"", name), errdetail("User does not have CONNECT privilege."))); /* * Check connection limit for this database. * * There is a race condition here --- we create our PGPROC before * checking for other PGPROCs. If two backends did this at about the * same time, they might both think they were over the limit, while * ideally one should succeed and one fail. Getting that to work * exactly seems more trouble than it is worth, however; instead we * just document that the connection limit is approximate. */ if (dbform->datconnlimit >= 0 && !am_superuser && CountDBBackends(MyDatabaseId) > dbform->datconnlimit) ereport(FATAL, (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("too many connections for database \"%s\"", name))); } /* * OK, we're golden. Next to-do item is to save the encoding info out of * the pg_database tuple. */ SetDatabaseEncoding(dbform->encoding); /* Record it as a GUC internal option, too */ SetConfigOption("server_encoding", GetDatabaseEncodingName(), PGC_INTERNAL, PGC_S_OVERRIDE); /* If we have no other source of client_encoding, use server encoding */ SetConfigOption("client_encoding", GetDatabaseEncodingName(), PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT); /* assign locale variables */ collate = NameStr(dbform->datcollate); ctype = NameStr(dbform->datctype); if (pg_perm_setlocale(LC_COLLATE, collate) == NULL) ereport(FATAL, (errmsg("database locale is incompatible with operating system"), errdetail("The database was initialized with LC_COLLATE \"%s\", " " which is not recognized by setlocale().", collate), errhint("Recreate the database with another locale or install the missing locale."))); if (pg_perm_setlocale(LC_CTYPE, ctype) == NULL) ereport(FATAL, (errmsg("database locale is incompatible with operating system"), errdetail("The database was initialized with LC_CTYPE \"%s\", " " which is not recognized by setlocale().", ctype), errhint("Recreate the database with another locale or install the missing locale."))); /* Make the locale settings visible as GUC variables, too */ SetConfigOption("lc_collate", collate, PGC_INTERNAL, PGC_S_OVERRIDE); SetConfigOption("lc_ctype", ctype, PGC_INTERNAL, PGC_S_OVERRIDE); ReleaseSysCache(tup); }
/* * CheckMyDatabase -- fetch information from the pg_database entry for our DB */ static void CheckMyDatabase(const char *name, bool am_superuser) { HeapTuple tup; Form_pg_database dbform; /* Fetch our pg_database row normally, via syscache */ tup = SearchSysCache(DATABASEOID, ObjectIdGetDatum(MyDatabaseId), 0, 0, 0); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); dbform = (Form_pg_database) GETSTRUCT(tup); /* This recheck is strictly paranoia */ if (strcmp(name, NameStr(dbform->datname)) != 0) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" has disappeared from pg_database", name), errdetail("Database OID %u now seems to belong to \"%s\".", MyDatabaseId, NameStr(dbform->datname)))); /* * Check permissions to connect to the database. * * These checks are not enforced when in standalone mode, so that there is * a way to recover from disabling all access to all databases, for * example "UPDATE pg_database SET datallowconn = false;". * * We do not enforce them for autovacuum worker processes either. */ if (IsUnderPostmaster && !IsAutoVacuumProcess()) { /* * Check that the database is currently allowing connections. */ if (!dbform->datallowconn) ereport(FATAL, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("database \"%s\" is not currently accepting connections", name))); /* * Check privilege to connect to the database. (The am_superuser test * is redundant, but since we have the flag, might as well check it * and save a few cycles.) */ if (!am_superuser && pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CONNECT) != ACLCHECK_OK) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied for database \"%s\"", name), errdetail("User does not have CONNECT privilege."))); /* * Check connection limit for this database. * * There is a race condition here --- we create our PGPROC before * checking for other PGPROCs. If two backends did this at about the * same time, they might both think they were over the limit, while * ideally one should succeed and one fail. Getting that to work * exactly seems more trouble than it is worth, however; instead we * just document that the connection limit is approximate. */ if (dbform->datconnlimit >= 0 && !am_superuser && CountDBBackends(MyDatabaseId) > dbform->datconnlimit) ereport(FATAL, (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("too many connections for database \"%s\"", name))); } /* * OK, we're golden. Next to-do item is to save the encoding info out of * the pg_database tuple. */ SetDatabaseEncoding(dbform->encoding); /* Record it as a GUC internal option, too */ SetConfigOption("server_encoding", GetDatabaseEncodingName(), PGC_INTERNAL, PGC_S_OVERRIDE); /* If we have no other source of client_encoding, use server encoding */ SetConfigOption("client_encoding", GetDatabaseEncodingName(), PGC_BACKEND, PGC_S_DEFAULT); /* Use the right encoding in translated messages */ #ifdef ENABLE_NLS pg_bind_textdomain_codeset(textdomain(NULL)); #endif /* * Lastly, set up any database-specific configuration variables. */ if (IsUnderPostmaster) { Datum datum; bool isnull; datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_datconfig, &isnull); if (!isnull) { ArrayType *a = DatumGetArrayTypeP(datum); ProcessGUCArray(a, PGC_S_DATABASE); } } ReleaseSysCache(tup); }
/* * Get the latestRemovedXid from the heap pages pointed at by the index * tuples being deleted. This puts the work for calculating latestRemovedXid * into the recovery path rather than the primary path. * * It's possible that this generates a fair amount of I/O, since an index * block may have hundreds of tuples being deleted. Repeat accesses to the * same heap blocks are common, though are not yet optimised. * * XXX optimise later with something like XLogPrefetchBuffer() */ static TransactionId btree_xlog_delete_get_latestRemovedXid(XLogReaderState *record) { xl_btree_delete *xlrec = (xl_btree_delete *) XLogRecGetData(record); OffsetNumber *unused; Buffer ibuffer, hbuffer; Page ipage, hpage; RelFileNode rnode; BlockNumber blkno; ItemId iitemid, hitemid; IndexTuple itup; HeapTupleHeader htuphdr; BlockNumber hblkno; OffsetNumber hoffnum; TransactionId latestRemovedXid = InvalidTransactionId; int i; /* * If there's nothing running on the standby we don't need to derive a * full latestRemovedXid value, so use a fast path out of here. This * returns InvalidTransactionId, and so will conflict with all HS * transactions; but since we just worked out that that's zero people, * it's OK. * * XXX There is a race condition here, which is that a new backend might * start just after we look. If so, it cannot need to conflict, but this * coding will result in throwing a conflict anyway. */ if (CountDBBackends(InvalidOid) == 0) return latestRemovedXid; /* * In what follows, we have to examine the previous state of the index * page, as well as the heap page(s) it points to. This is only valid if * WAL replay has reached a consistent database state; which means that * the preceding check is not just an optimization, but is *necessary*. We * won't have let in any user sessions before we reach consistency. */ if (!reachedConsistency) elog(PANIC, "btree_xlog_delete_get_latestRemovedXid: cannot operate with inconsistent data"); /* * Get index page. If the DB is consistent, this should not fail, nor * should any of the heap page fetches below. If one does, we return * InvalidTransactionId to cancel all HS transactions. That's probably * overkill, but it's safe, and certainly better than panicking here. */ XLogRecGetBlockTag(record, 0, &rnode, NULL, &blkno); ibuffer = XLogReadBufferExtended(rnode, MAIN_FORKNUM, blkno, RBM_NORMAL); if (!BufferIsValid(ibuffer)) return InvalidTransactionId; LockBuffer(ibuffer, BT_READ); ipage = (Page) BufferGetPage(ibuffer); /* * Loop through the deleted index items to obtain the TransactionId from * the heap items they point to. */ unused = (OffsetNumber *) ((char *) xlrec + SizeOfBtreeDelete); for (i = 0; i < xlrec->nitems; i++) { /* * Identify the index tuple about to be deleted */ iitemid = PageGetItemId(ipage, unused[i]); itup = (IndexTuple) PageGetItem(ipage, iitemid); /* * Locate the heap page that the index tuple points at */ hblkno = ItemPointerGetBlockNumber(&(itup->t_tid)); hbuffer = XLogReadBufferExtended(xlrec->hnode, MAIN_FORKNUM, hblkno, RBM_NORMAL); if (!BufferIsValid(hbuffer)) { UnlockReleaseBuffer(ibuffer); return InvalidTransactionId; } LockBuffer(hbuffer, BUFFER_LOCK_SHARE); hpage = (Page) BufferGetPage(hbuffer); /* * Look up the heap tuple header that the index tuple points at by * using the heap node supplied with the xlrec. We can't use * heap_fetch, since it uses ReadBuffer rather than XLogReadBuffer. * Note that we are not looking at tuple data here, just headers. */ hoffnum = ItemPointerGetOffsetNumber(&(itup->t_tid)); hitemid = PageGetItemId(hpage, hoffnum); /* * Follow any redirections until we find something useful. */ while (ItemIdIsRedirected(hitemid)) { hoffnum = ItemIdGetRedirect(hitemid); hitemid = PageGetItemId(hpage, hoffnum); CHECK_FOR_INTERRUPTS(); } /* * If the heap item has storage, then read the header and use that to * set latestRemovedXid. * * Some LP_DEAD items may not be accessible, so we ignore them. */ if (ItemIdHasStorage(hitemid)) { htuphdr = (HeapTupleHeader) PageGetItem(hpage, hitemid); HeapTupleHeaderAdvanceLatestRemovedXid(htuphdr, &latestRemovedXid); } else if (ItemIdIsDead(hitemid)) { /* * Conjecture: if hitemid is dead then it had xids before the xids * marked on LP_NORMAL items. So we just ignore this item and move * onto the next, for the purposes of calculating * latestRemovedxids. */ } else Assert(!ItemIdIsUsed(hitemid)); UnlockReleaseBuffer(hbuffer); } UnlockReleaseBuffer(ibuffer); /* * If all heap tuples were LP_DEAD then we will be returning * InvalidTransactionId here, which avoids conflicts. This matches * existing logic which assumes that LP_DEAD tuples must already be older * than the latestRemovedXid on the cleanup record that set them as * LP_DEAD, hence must already have generated a conflict. */ return latestRemovedXid; }