#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/latch.h"
+#include "storage/lmgr.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
#define MIN_AUTOVAC_SLEEPTIME 100.0 /* milliseconds */
#define MAX_AUTOVAC_SLEEPTIME 300 /* seconds */
-/*
- * Maximum number of orphan temporary tables to drop in a single transaction.
- * (If this is too high, we might run out of heavyweight locks.)
- */
-#define MAX_ORPHAN_ITEMS 50
-
-/*
- * After this many failures, stop trying to drop orphan temporary tables.
- */
-#define MAX_ORPHAN_DROP_FAILURE 10
-
/* Flags to tell if we are in an autovacuum process */
static bool am_autovacuum_launcher = false;
static bool am_autovacuum_worker = false;
Form_pg_database dbForm;
List *table_oids = NIL;
List *orphan_oids = NIL;
- List *pending_oids = NIL;
HASHCTL ctl;
HTAB *table_toast_map;
ListCell *volatile cell;
BufferAccessStrategy bstrategy;
ScanKeyData key;
TupleDesc pg_class_desc;
- int orphan_failures = 0;
int effective_multixact_freeze_max_age;
/*
* TOAST tables. The reason for doing the second pass is that during it we
* want to use the main relation's pg_class.reloptions entry if the TOAST
* table does not have any, and we cannot obtain it unless we know
- * beforehand what's the main table OID.
+ * beforehand what's the main table OID.
*
* We need to check TOAST tables separately because in cases with short,
* wide tables there might be proportionally much more activity in the
relid = HeapTupleGetOid(tuple);
- /* Fetch reloptions and the pgstat entry for this table */
- relopts = extract_autovac_opts(tuple, pg_class_desc);
- tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared,
- shared, dbentry);
-
- /* Check if it needs vacuum or analyze */
- relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
- effective_multixact_freeze_max_age,
- &dovacuum, &doanalyze, &wraparound);
-
/*
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
backendID = GetTempNamespaceBackendId(classForm->relnamespace);
/* We just ignore it if the owning backend is still active */
- if (backendID == MyBackendId || BackendIdGetProc(backendID) == NULL)
+ if (backendID != InvalidBackendId &&
+ (backendID == MyBackendId ||
+ BackendIdGetProc(backendID) == NULL))
{
/*
- * We found an orphan temp table which was probably left
- * behind by a crashed backend. Remember it, so we can attempt
- * to drop it.
+ * The table seems to be orphaned -- although it might be that
+ * the owning backend has already deleted it and exited; our
+ * pg_class scan snapshot is not necessarily up-to-date
+ * anymore, so we could be looking at a committed-dead entry.
+ * Remember it so we can try to delete it later.
*/
orphan_oids = lappend_oid(orphan_oids, relid);
}
+ continue;
}
- else
- {
- /* relations that need work are added to table_oids */
- if (dovacuum || doanalyze)
- table_oids = lappend_oid(table_oids, relid);
- /*
- * Remember the association for the second pass. Note: we must do
- * this even if the table is going to be vacuumed, because we
- * don't automatically vacuum toast tables along the parent table.
- */
- if (OidIsValid(classForm->reltoastrelid))
- {
- av_relation *hentry;
- bool found;
+ /* Fetch reloptions and the pgstat entry for this table */
+ relopts = extract_autovac_opts(tuple, pg_class_desc);
+ tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared,
+ shared, dbentry);
+
+ /* Check if it needs vacuum or analyze */
+ relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
+ effective_multixact_freeze_max_age,
+ &dovacuum, &doanalyze, &wraparound);
+
+ /* Relations that need work are added to table_oids */
+ if (dovacuum || doanalyze)
+ table_oids = lappend_oid(table_oids, relid);
+
+ /*
+ * Remember TOAST associations for the second pass. Note: we must do
+ * this whether or not the table is going to be vacuumed, because we
+ * don't automatically vacuum toast tables along the parent table.
+ */
+ if (OidIsValid(classForm->reltoastrelid))
+ {
+ av_relation *hentry;
+ bool found;
- hentry = hash_search(table_toast_map,
- &classForm->reltoastrelid,
- HASH_ENTER, &found);
+ hentry = hash_search(table_toast_map,
+ &classForm->reltoastrelid,
+ HASH_ENTER, &found);
- if (!found)
+ if (!found)
+ {
+ /* hash_search already filled in the key */
+ hentry->ar_relid = relid;
+ hentry->ar_hasrelopts = false;
+ if (relopts != NULL)
{
- /* hash_search already filled in the key */
- hentry->ar_relid = relid;
- hentry->ar_hasrelopts = false;
- if (relopts != NULL)
- {
- hentry->ar_hasrelopts = true;
- memcpy(&hentry->ar_reloptions, relopts,
- sizeof(AutoVacOpts));
- }
+ hentry->ar_hasrelopts = true;
+ memcpy(&hentry->ar_reloptions, relopts,
+ sizeof(AutoVacOpts));
}
}
}
heap_close(classRel, AccessShareLock);
/*
- * Loop through orphan temporary tables and drop them in batches. If
- * we're unable to drop one particular table, we'll retry to see if we
- * can drop others, but if we fail too many times we'll give up and proceed
- * with our regular work, so that this step hopefully can't wedge
- * autovacuum for too long.
+ * Recheck orphan temporary tables, and if they still seem orphaned, drop
+ * them. We'll eat a transaction per dropped table, which might seem
+ * excessive, but we should only need to do anything as a result of a
+ * previous backend crash, so this should not happen often enough to
+ * justify "optimizing". Using separate transactions ensures that we
+ * don't bloat the lock table if there are many temp tables to be dropped,
+ * and it ensures that we don't lose work if a deletion attempt fails.
*/
- while (list_length(orphan_oids) > 0 &&
- orphan_failures < MAX_ORPHAN_DROP_FAILURE)
+ foreach(cell, orphan_oids)
{
- Oid relid = linitial_oid(orphan_oids);
- ObjectAddress object;
- char *namespace = get_namespace_name(get_rel_namespace(relid));
- char *relname = get_rel_name(relid);
+ Oid relid = lfirst_oid(cell);
+ Form_pg_class classForm;
+ int backendID;
+ ObjectAddress object;
- orphan_oids = list_delete_first(orphan_oids);
+ /*
+ * Check for user-requested abort.
+ */
+ CHECK_FOR_INTERRUPTS();
- PG_TRY();
- {
- ereport(LOG,
- (errmsg("autovacuum: dropping orphan temp table \"%s\".\"%s\" in database \"%s\"",
- namespace, relname,
- get_database_name(MyDatabaseId))));
- object.classId = RelationRelationId;
- object.objectId = relid;
- object.objectSubId = 0;
- performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL);
+ /*
+ * Try to lock the table. If we can't get the lock immediately,
+ * somebody else is using (or dropping) the table, so it's not our
+ * concern anymore. Having the lock prevents race conditions below.
+ */
+ if (!ConditionalLockRelationOid(relid, AccessExclusiveLock))
+ continue;
- /*
- * This orphan table has been dropped correctly, add it to the
- * list of tables whose drop should be attempted again if an
- * error after in the same transaction.
- */
- pending_oids = lappend_oid(pending_oids, relid);
- }
- PG_CATCH();
+ /*
+ * Re-fetch the pg_class tuple and re-check whether it still seems to
+ * be an orphaned temp table. If it's not there or no longer the same
+ * relation, ignore it.
+ */
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
{
- /* Abort the current transaction. */
- HOLD_INTERRUPTS();
-
- errcontext("dropping of orphan temp table \"%s\".\"%s\" in database \"%s\"",
- namespace, relname,
- get_database_name(MyDatabaseId));
-
- EmitErrorReport();
-
- /* this resets the PGXACT flags too */
- AbortOutOfAnyTransaction();
- FlushErrorState();
-
- /*
- * Any tables were succesfully dropped before the failure now
- * need to be dropped again. Add them back into the list, but
- * don't retry the table that failed.
- */
- orphan_oids = list_concat(orphan_oids, pending_oids);
- orphan_failures++;
-
- /* Start a new transaction. */
- StartTransactionCommand();
-
- /* StartTransactionCommand changed elsewhere the memory context */
- MemoryContextSwitchTo(AutovacMemCxt);
-
- RESUME_INTERRUPTS();
+ /* be sure to drop useless lock so we don't bloat lock table */
+ UnlockRelationOid(relid, AccessExclusiveLock);
+ continue;
}
- PG_END_TRY();
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
- * If we've successfully dropped quite a few tables, commit the
- * transaction and begin a new one. The main point of this is to
- * avoid accumulating too many locks and blowing out the lock table,
- * but it also minimizes the amount of work that will have to be rolled
- * back if we fail to drop some table later in the list.
+ * Make all the same tests made in the loop above. In event of OID
+ * counter wraparound, the pg_class entry we have now might be
+ * completely unrelated to the one we saw before.
*/
- if (list_length(pending_oids) >= MAX_ORPHAN_ITEMS)
+ if (!((classForm->relkind == RELKIND_RELATION ||
+ classForm->relkind == RELKIND_MATVIEW) &&
+ classForm->relpersistence == RELPERSISTENCE_TEMP))
{
- CommitTransactionCommand();
- StartTransactionCommand();
-
- /* StartTransactionCommand changed elsewhere */
- MemoryContextSwitchTo(AutovacMemCxt);
-
- list_free(pending_oids);
- pending_oids = NIL;
+ UnlockRelationOid(relid, AccessExclusiveLock);
+ continue;
+ }
+ backendID = GetTempNamespaceBackendId(classForm->relnamespace);
+ if (!(backendID != InvalidBackendId &&
+ (backendID == MyBackendId ||
+ BackendIdGetProc(backendID) == NULL)))
+ {
+ UnlockRelationOid(relid, AccessExclusiveLock);
+ continue;
}
- pfree(relname);
- pfree(namespace);
- }
+ /* OK, let's delete it */
+ ereport(LOG,
+ (errmsg("autovacuum: dropping orphan temp table \"%s.%s.%s\"",
+ get_database_name(MyDatabaseId),
+ get_namespace_name(classForm->relnamespace),
+ NameStr(classForm->relname))));
- /*
- * Commit current transaction to finish the cleanup done previously and
- * restart a new one to not bloat the activity of the following steps.
- * This needs to happen only if there are any items thought as previously
- * pending, but are actually not as the last transaction doing the cleanup
- * has been successful.
- */
- if (list_length(pending_oids) > 0)
- {
+ object.classId = RelationRelationId;
+ object.objectId = relid;
+ object.objectSubId = 0;
+ performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL);
+
+ /*
+ * To commit the deletion, end current transaction and start a new
+ * one. Note this also releases the lock we took.
+ */
CommitTransactionCommand();
StartTransactionCommand();
- /* StartTransactionCommand changed elsewhere */
+ /* StartTransactionCommand changed current memory context */
MemoryContextSwitchTo(AutovacMemCxt);
-
- list_free(pending_oids);
}
/*