* worker and dealt with just by having the worker exit normally. The launcher
* will launch a new worker again later, per schedule.
*
- * When the worker is done vacuuming it sends SIGUSR1 to the launcher. The
+ * When the worker is done vacuuming it sends SIGUSR2 to the launcher. The
* launcher then wakes up and is able to launch another worker, if the schedule
* is so tight that a new worker is needed immediately. At this time the
* launcher can also balance the settings for the various remaining workers'
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.103 2009/08/27 17:18:44 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.104 2009/08/31 19:40:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include <time.h>
#include <unistd.h>
-#include "access/genam.h"
#include "access/heapam.h"
#include "access/reloptions.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/dependency.h"
-#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "commands/dbcommands.h"
#include "commands/vacuum.h"
-#include "libpq/hba.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/fork_process.h"
#include "postmaster/postmaster.h"
#include "storage/bufmgr.h"
-#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
-#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
-#include "utils/dynahash.h"
-#include "utils/flatfiles.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
+#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
/* Flags set by signal handlers */
static volatile sig_atomic_t got_SIGHUP = false;
-static volatile sig_atomic_t got_SIGUSR1 = false;
+static volatile sig_atomic_t got_SIGUSR2 = false;
static volatile sig_atomic_t got_SIGTERM = false;
/* Comparison point for determining whether freeze_max_age is exceeded */
PgStat_StatDBEntry *dbentry);
static void autovac_report_activity(autovac_table *tab);
static void avl_sighup_handler(SIGNAL_ARGS);
-static void avl_sigusr1_handler(SIGNAL_ARGS);
+static void avl_sigusr2_handler(SIGNAL_ARGS);
static void avl_sigterm_handler(SIGNAL_ARGS);
-static void avl_quickdie(SIGNAL_ARGS);
static void autovac_refresh_stats(void);
/* Identify myself via ps */
init_ps_display("autovacuum launcher process", "", "", "");
+ ereport(LOG,
+ (errmsg("autovacuum launcher started")));
+
if (PostAuthDelay)
pg_usleep(PostAuthDelay * 1000000L);
#endif
/*
- * Set up signal handlers. Since this is an auxiliary process, it has
- * particular signal requirements -- no deadlock checker or sinval
- * catchup, for example.
+ * Set up signal handlers. We operate on databases much like a regular
+ * backend, so we use the same signal handling. See equivalent code in
+ * tcop/postgres.c.
*/
pqsignal(SIGHUP, avl_sighup_handler);
-
- pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGINT, StatementCancelHandler);
pqsignal(SIGTERM, avl_sigterm_handler);
- pqsignal(SIGQUIT, avl_quickdie);
- pqsignal(SIGALRM, SIG_IGN);
+
+ pqsignal(SIGQUIT, quickdie);
+ pqsignal(SIGALRM, handle_sig_alarm);
pqsignal(SIGPIPE, SIG_IGN);
- pqsignal(SIGUSR1, avl_sigusr1_handler);
- pqsignal(SIGUSR2, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, avl_sigusr2_handler);
pqsignal(SIGFPE, FloatExceptionHandler);
pqsignal(SIGCHLD, SIG_DFL);
* had to do some stuff with LWLocks).
*/
#ifndef EXEC_BACKEND
- InitAuxiliaryProcess();
+ InitProcess();
#endif
+ InitPostgres(NULL, InvalidOid, NULL, NULL);
+
+ SetProcessingMode(NormalProcessing);
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContextSwitchTo(AutovacMemCxt);
-
/*
* If an exception is encountered, processing resumes here.
*
- * This code is heavily based on bgwriter.c, q.v.
+ * This code is a stripped down version of PostgresMain error recovery.
*/
if (sigsetjmp(local_sigjmp_buf, 1) != 0)
{
/* Prevents interrupts while cleaning up */
HOLD_INTERRUPTS();
+ /* Forget any pending QueryCancel request */
+ QueryCancelPending = false;
+ disable_sig_alarm(true);
+ QueryCancelPending = false; /* again in case timeout occurred */
+
/* Report the error to the server log */
EmitErrorReport();
- /*
- * These operations are really just a minimal subset of
- * AbortTransaction(). We don't have very many resources to worry
- * about, but we do have LWLocks.
- */
- LWLockReleaseAll();
- AtEOXact_Files();
- AtEOXact_HashTables(false);
+ /* Abort the current transaction in order to recover */
+ AbortCurrentTransaction();
/*
* Now return to normal top-level context and clear ErrorContext for
/* We can now handle ereport(ERROR) */
PG_exception_stack = &local_sigjmp_buf;
- ereport(LOG,
- (errmsg("autovacuum launcher started")));
-
/* must unblock signals before calling rebuild_database_list */
PG_SETMASK(&UnBlockSig);
* necessity for manual cleanup of all postmaster children.
*/
if (!PostmasterIsAlive(true))
- exit(1);
+ proc_exit(1);
launcher_determine_sleep((AutoVacuumShmem->av_freeWorkers != NULL),
false, &nap);
+ /* Allow sinval catchup interrupts while sleeping */
+ EnableCatchupInterrupt();
+
/*
* Sleep for a while according to schedule.
*
* necessity for manual cleanup of all postmaster children.
*/
if (!PostmasterIsAlive(true))
- exit(1);
+ proc_exit(1);
- if (got_SIGTERM || got_SIGHUP || got_SIGUSR1)
+ if (got_SIGTERM || got_SIGHUP || got_SIGUSR2)
break;
}
+ DisableCatchupInterrupt();
+
/* the normal shutdown case */
if (got_SIGTERM)
break;
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
- /* shutdown requested in config file */
+ /* shutdown requested in config file? */
if (!AutoVacuumingActive())
break;
* a worker finished, or postmaster signalled failure to start a
* worker
*/
- if (got_SIGUSR1)
+ if (got_SIGUSR2)
{
- got_SIGUSR1 = false;
+ got_SIGUSR2 = false;
/* rebalance cost limits, if needed */
if (AutoVacuumShmem->av_signal[AutoVacRebalance])
/*
* Called from postmaster to signal a failure to fork a process to become
- * worker. The postmaster should kill(SIGUSR1) the launcher shortly
+ * worker. The postmaster should kill(SIGUSR2) the launcher shortly
* after calling this function.
*/
void
got_SIGHUP = true;
}
-/* SIGUSR1: a worker is up and running, or just finished, or failed to fork */
+/* SIGUSR2: a worker is up and running, or just finished, or failed to fork */
static void
-avl_sigusr1_handler(SIGNAL_ARGS)
+avl_sigusr2_handler(SIGNAL_ARGS)
{
- got_SIGUSR1 = true;
+ got_SIGUSR2 = true;
}
/* SIGTERM: time to die */
got_SIGTERM = true;
}
-/*
- * avl_quickdie occurs when signalled SIGQUIT from postmaster.
- *
- * Some backend has bought the farm, so we need to stop what we're doing
- * and exit.
- */
-static void
-avl_quickdie(SIGNAL_ARGS)
-{
- PG_SETMASK(&BlockSig);
-
- /*
- * We DO NOT want to run proc_exit() callbacks -- we're here because
- * shared memory may be corrupted, so we don't want to try to clean up our
- * transaction. Just nail the windows shut and get out of town. Now that
- * there's an atexit callback to prevent third-party code from breaking
- * things by calling exit() directly, we have to reset the callbacks
- * explicitly to make this work as intended.
- */
- on_exit_reset();
-
- /*
- * Note we do exit(2) not exit(0). This is to force the postmaster into a
- * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- exit(2);
-}
-
/********************************************************************
* AUTOVACUUM WORKER CODE
/* wake up the launcher */
if (AutoVacuumShmem->av_launcherpid != 0)
- kill(AutoVacuumShmem->av_launcherpid, SIGUSR1);
+ kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
}
else
{
/*
* get_database_list
+ * Return a list of all databases found in pg_database.
*
- * Return a list of all databases. Note we cannot use pg_database,
- * because we aren't connected; we use the flat database file.
+ * Note: this is the only function in which the autovacuum launcher uses a
+ * transaction. Although we aren't attached to any particular database and
+ * therefore can't access most catalogs, we do have enough infrastructure
+ * to do a seqscan on pg_database.
*/
static List *
get_database_list(void)
{
- char *filename;
List *dblist = NIL;
- char thisname[NAMEDATALEN];
- FILE *db_file;
- Oid db_id;
- Oid db_tablespace;
- TransactionId db_frozenxid;
-
- filename = database_getflatfilename();
- db_file = AllocateFile(filename, "r");
- if (db_file == NULL)
- ereport(FATAL,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m", filename)));
+ Relation rel;
+ HeapScanDesc scan;
+ HeapTuple tup;
+
+ /*
+ * Start a transaction so we can access pg_database, and get a snapshot.
+ * We don't have a use for the snapshot itself, but we're interested in
+ * the secondary effect that it sets RecentGlobalXmin. (This is critical
+ * for anything that reads heap pages, because HOT may decide to prune
+ * them even if the process doesn't attempt to modify any tuples.)
+ */
+ StartTransactionCommand();
+ (void) GetTransactionSnapshot();
+
+ /* Allocate our results in AutovacMemCxt, not transaction context */
+ MemoryContextSwitchTo(AutovacMemCxt);
- while (read_pg_database_line(db_file, thisname, &db_id,
- &db_tablespace, &db_frozenxid))
+ rel = heap_open(DatabaseRelationId, AccessShareLock);
+ scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
+
+ while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
{
- avw_dbase *avdb;
+ Form_pg_database pgdatabase = (Form_pg_database) GETSTRUCT(tup);
+ avw_dbase *avdb;
avdb = (avw_dbase *) palloc(sizeof(avw_dbase));
- avdb->adw_datid = db_id;
- avdb->adw_name = pstrdup(thisname);
- avdb->adw_frozenxid = db_frozenxid;
+ avdb->adw_datid = HeapTupleGetOid(tup);
+ avdb->adw_name = pstrdup(NameStr(pgdatabase->datname));
+ avdb->adw_frozenxid = pgdatabase->datfrozenxid;
/* this gets set later: */
avdb->adw_entry = NULL;
dblist = lappend(dblist, avdb);
}
- FreeFile(db_file);
- pfree(filename);
+ heap_endscan(scan);
+ heap_close(rel, AccessShareLock);
+
+ CommitTransactionCommand();
return dblist;
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.208 2009/08/12 20:53:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.209 2009/08/31 19:41:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
size = add_size(size, sizeof(PROC_HDR));
/* AuxiliaryProcs */
size = add_size(size, mul_size(NUM_AUXILIARY_PROCS, sizeof(PGPROC)));
- /* MyProcs, including autovacuum */
+ /* MyProcs, including autovacuum workers and launcher */
size = add_size(size, mul_size(MaxBackends, sizeof(PGPROC)));
/* ProcStructLock */
size = add_size(size, sizeof(slock_t));
ProcGlobal->freeProcs = &procs[i];
}
- procs = (PGPROC *) ShmemAlloc((autovacuum_max_workers) * sizeof(PGPROC));
+ /*
+ * Likewise for the PGPROCs reserved for autovacuum.
+ *
+ * Note: the "+1" here accounts for the autovac launcher
+ */
+ procs = (PGPROC *) ShmemAlloc((autovacuum_max_workers + 1) * sizeof(PGPROC));
if (!procs)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of shared memory")));
- MemSet(procs, 0, autovacuum_max_workers * sizeof(PGPROC));
- for (i = 0; i < autovacuum_max_workers; i++)
+ MemSet(procs, 0, (autovacuum_max_workers + 1) * sizeof(PGPROC));
+ for (i = 0; i < autovacuum_max_workers + 1; i++)
{
PGSemaphoreCreate(&(procs[i].sem));
procs[i].links.next = (SHM_QUEUE *) ProcGlobal->autovacFreeProcs;
ProcGlobal->autovacFreeProcs = &procs[i];
}
+ /*
+ * And auxiliary procs.
+ */
MemSet(AuxiliaryProcs, 0, NUM_AUXILIARY_PROCS * sizeof(PGPROC));
for (i = 0; i < NUM_AUXILIARY_PROCS; i++)
{
set_spins_per_delay(procglobal->spins_per_delay);
- if (IsAutoVacuumWorkerProcess())
+ if (IsAnyAutoVacuumProcess())
MyProc = procglobal->autovacFreeProcs;
else
MyProc = procglobal->freeProcs;
if (MyProc != NULL)
{
- if (IsAutoVacuumWorkerProcess())
+ if (IsAnyAutoVacuumProcess())
procglobal->autovacFreeProcs = (PGPROC *) MyProc->links.next;
else
procglobal->freeProcs = (PGPROC *) MyProc->links.next;
/*
* Now that we have a PGPROC, mark ourselves as an active postmaster
* child; this is so that the postmaster can detect it if we exit without
- * cleaning up.
+ * cleaning up. (XXX autovac launcher currently doesn't participate in
+ * this; it probably should.)
*/
- if (IsUnderPostmaster)
+ if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess())
MarkPostmasterChildActive();
/*
MyProc->roleId = InvalidOid;
MyProc->inCommit = false;
MyProc->vacuumFlags = 0;
+ /* NB -- autovac launcher intentionally does not set IS_AUTOVACUUM */
if (IsAutoVacuumWorkerProcess())
MyProc->vacuumFlags |= PROC_IS_AUTOVACUUM;
MyProc->lwWaiting = false;
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->inCommit = false;
- /* we don't set the "is autovacuum" flag in the launcher */
MyProc->vacuumFlags = 0;
MyProc->lwWaiting = false;
MyProc->lwExclusive = false;
SpinLockAcquire(ProcStructLock);
- /* Return PGPROC structure (and semaphore) to freelist */
- if (IsAutoVacuumWorkerProcess())
+ /* Return PGPROC structure (and semaphore) to appropriate freelist */
+ if (IsAnyAutoVacuumProcess())
{
MyProc->links.next = (SHM_QUEUE *) procglobal->autovacFreeProcs;
procglobal->autovacFreeProcs = MyProc;
/*
* This process is no longer present in shared memory in any meaningful
* way, so tell the postmaster we've cleaned up acceptably well.
+ * (XXX autovac launcher should be included here someday)
*/
- if (IsUnderPostmaster)
+ if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess())
MarkPostmasterChildInactive();
/* wake autovac launcher if needed -- see comments in FreeWorkerInfo */
if (AutovacuumLauncherPid != 0)
- kill(AutovacuumLauncherPid, SIGUSR1);
+ kill(AutovacuumLauncherPid, SIGUSR2);
}
/*