@ -92,7 +92,9 @@
# include "storage/procsignal.h"
# include "storage/sinvaladt.h"
# include "tcop/tcopprot.h"
# include "utils/dsa.h"
# include "utils/fmgroids.h"
# include "utils/fmgrprotos.h"
# include "utils/lsyscache.h"
# include "utils/memutils.h"
# include "utils/ps_status.h"
@ -252,9 +254,10 @@ typedef enum
* av_runningWorkers the WorkerInfo non - free queue
* av_startingWorker pointer to WorkerInfo currently being started ( cleared by
* the worker itself as soon as it ' s up and running )
* av_dsa_handle handle for allocatable shared memory
*
* This struct is protected by AutovacuumLock , except for av_signal and parts
* of the worker list ( see above ) .
* of the worker list ( see above ) . av_dsa_handle is readable unlocked .
* - - - - - - - - - - - - -
*/
typedef struct
@ -264,6 +267,8 @@ typedef struct
dlist_head av_freeWorkers ;
dlist_head av_runningWorkers ;
WorkerInfo av_startingWorker ;
dsa_handle av_dsa_handle ;
dsa_pointer av_workitems ;
} AutoVacuumShmemStruct ;
static AutoVacuumShmemStruct * AutoVacuumShmem ;
@ -278,6 +283,32 @@ static MemoryContext DatabaseListCxt = NULL;
/* Pointer to my own WorkerInfo, valid on each worker */
static WorkerInfo MyWorkerInfo = NULL ;
/*
* Autovacuum workitem array , stored in AutoVacuumShmem - > av_workitems . This
* list is mostly protected by AutovacuumLock , except that if it ' s marked
* ' active ' other processes must not modify the work - identifying members ,
* though changing the list pointers is okay .
*/
typedef struct AutoVacuumWorkItem
{
AutoVacuumWorkItemType avw_type ;
Oid avw_database ;
Oid avw_relation ;
BlockNumber avw_blockNumber ;
bool avw_active ;
dsa_pointer avw_next ; /* doubly linked list pointers */
dsa_pointer avw_prev ;
} AutoVacuumWorkItem ;
# define NUM_WORKITEMS 256
typedef struct
{
dsa_pointer avs_usedItems ;
dsa_pointer avs_freeItems ;
} AutovacWorkItems ;
static dsa_area * AutoVacuumDSA = NULL ;
/* PID of launcher, valid only in worker while shutting down */
int AutovacuumLauncherPid = 0 ;
@ -316,11 +347,16 @@ static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
static PgStat_StatTabEntry * get_pgstat_tabentry_relid ( Oid relid , bool isshared ,
PgStat_StatDBEntry * shared ,
PgStat_StatDBEntry * dbentry ) ;
static void perform_work_item ( AutoVacuumWorkItem * workitem ) ;
static void autovac_report_activity ( autovac_table * tab ) ;
static void autovac_report_workitem ( AutoVacuumWorkItem * workitem ,
const char * nspname , const char * relname ) ;
static void av_sighup_handler ( SIGNAL_ARGS ) ;
static void avl_sigusr2_handler ( SIGNAL_ARGS ) ;
static void avl_sigterm_handler ( SIGNAL_ARGS ) ;
static void autovac_refresh_stats ( void ) ;
static void remove_wi_from_list ( dsa_pointer * list , dsa_pointer wi_ptr ) ;
static void add_wi_to_list ( dsa_pointer * list , dsa_pointer wi_ptr ) ;
@ -574,6 +610,28 @@ AutoVacLauncherMain(int argc, char *argv[])
*/
rebuild_database_list ( InvalidOid ) ;
/*
* Set up our DSA so that backends can install work - item requests . It may
* already exist as created by a previous launcher .
*/
if ( ! AutoVacuumShmem - > av_dsa_handle )
{
LWLockAcquire ( AutovacuumLock , LW_EXCLUSIVE ) ;
AutoVacuumDSA = dsa_create ( AutovacuumLock - > tranche ) ;
/* make sure it doesn't go away even if we do */
dsa_pin ( AutoVacuumDSA ) ;
dsa_pin_mapping ( AutoVacuumDSA ) ;
AutoVacuumShmem - > av_dsa_handle = dsa_get_handle ( AutoVacuumDSA ) ;
/* delay array allocation until first request */
AutoVacuumShmem - > av_workitems = InvalidDsaPointer ;
LWLockRelease ( AutovacuumLock ) ;
}
else
{
AutoVacuumDSA = dsa_attach ( AutoVacuumShmem - > av_dsa_handle ) ;
dsa_pin_mapping ( AutoVacuumDSA ) ;
}
/* loop until shutdown request */
while ( ! got_SIGTERM )
{
@ -1617,6 +1675,14 @@ AutoVacWorkerMain(int argc, char *argv[])
{
char dbname [ NAMEDATALEN ] ;
if ( AutoVacuumShmem - > av_dsa_handle )
{
/* First use of DSA in this worker, so attach to it */
Assert ( ! AutoVacuumDSA ) ;
AutoVacuumDSA = dsa_attach ( AutoVacuumShmem - > av_dsa_handle ) ;
dsa_pin_mapping ( AutoVacuumDSA ) ;
}
/*
* Report autovac startup to the stats collector . We deliberately do
* this before InitPostgres , so that the last_autovac_time will get
@ -2466,6 +2532,69 @@ deleted:
VacuumCostLimit = stdVacuumCostLimit ;
}
/*
* Perform additional work items , as requested by backends .
*/
if ( AutoVacuumShmem - > av_workitems )
{
dsa_pointer wi_ptr ;
AutovacWorkItems * workitems ;
LWLockAcquire ( AutovacuumLock , LW_EXCLUSIVE ) ;
/*
* Scan the list of pending items , and process the inactive ones in
* our database .
*/
workitems = ( AutovacWorkItems * )
dsa_get_address ( AutoVacuumDSA , AutoVacuumShmem - > av_workitems ) ;
wi_ptr = workitems - > avs_usedItems ;
while ( wi_ptr ! = InvalidDsaPointer )
{
AutoVacuumWorkItem * workitem ;
workitem = ( AutoVacuumWorkItem * )
dsa_get_address ( AutoVacuumDSA , wi_ptr ) ;
if ( workitem - > avw_database = = MyDatabaseId & & ! workitem - > avw_active )
{
dsa_pointer next_ptr ;
/* claim this one */
workitem - > avw_active = true ;
LWLockRelease ( AutovacuumLock ) ;
perform_work_item ( workitem ) ;
/*
* Check for config changes before acquiring lock for further
* jobs .
*/
CHECK_FOR_INTERRUPTS ( ) ;
if ( got_SIGHUP )
{
got_SIGHUP = false ;
ProcessConfigFile ( PGC_SIGHUP ) ;
}
LWLockAcquire ( AutovacuumLock , LW_EXCLUSIVE ) ;
/* Put the array item back for the next user */
next_ptr = workitem - > avw_next ;
remove_wi_from_list ( & workitems - > avs_usedItems , wi_ptr ) ;
add_wi_to_list ( & workitems - > avs_freeItems , wi_ptr ) ;
wi_ptr = next_ptr ;
}
else
wi_ptr = workitem - > avw_next ;
}
/* all done */
LWLockRelease ( AutovacuumLock ) ;
}
/*
* We leak table_toast_map here ( among other things ) , but since we ' re
* going away soon , it ' s not a problem .
@ -2498,6 +2627,103 @@ deleted:
CommitTransactionCommand ( ) ;
}
/*
* Execute a previously registered work item .
*/
static void
perform_work_item ( AutoVacuumWorkItem * workitem )
{
char * cur_datname = NULL ;
char * cur_nspname = NULL ;
char * cur_relname = NULL ;
/*
* Note we do not store table info in MyWorkerInfo , since this is not
* vacuuming proper .
*/
/*
* Save the relation name for a possible error message , to avoid a catalog
* lookup in case of an error . If any of these return NULL , then the
* relation has been dropped since last we checked ; skip it . Note : they
* must live in a long - lived memory context because we call vacuum and
* analyze in different transactions .
*/
cur_relname = get_rel_name ( workitem - > avw_relation ) ;
cur_nspname = get_namespace_name ( get_rel_namespace ( workitem - > avw_relation ) ) ;
cur_datname = get_database_name ( MyDatabaseId ) ;
if ( ! cur_relname | | ! cur_nspname | | ! cur_datname )
goto deleted2 ;
autovac_report_workitem ( workitem , cur_nspname , cur_datname ) ;
/*
* We will abort the current work item if something errors out , and
* continue with the next one ; in particular , this happens if we are
* interrupted with SIGINT . Note that this means that the work item list
* can be lossy .
*/
PG_TRY ( ) ;
{
/* have at it */
MemoryContextSwitchTo ( TopTransactionContext ) ;
switch ( workitem - > avw_type )
{
case AVW_BRINSummarizeRange :
DirectFunctionCall2 ( brin_summarize_range ,
ObjectIdGetDatum ( workitem - > avw_relation ) ,
Int64GetDatum ( ( int64 ) workitem - > avw_blockNumber ) ) ;
break ;
default :
elog ( WARNING , " unrecognized work item found: type %d " ,
workitem - > avw_type ) ;
break ;
}
/*
* Clear a possible query - cancel signal , to avoid a late reaction to
* an automatically - sent signal because of vacuuming the current table
* ( we ' re done with it , so it would make no sense to cancel at this
* point . )
*/
QueryCancelPending = false ;
}
PG_CATCH ( ) ;
{
/*
* Abort the transaction , start a new one , and proceed with the next
* table in our list .
*/
HOLD_INTERRUPTS ( ) ;
errcontext ( " processing work entry for relation \" %s.%s.%s \" " ,
cur_datname , cur_nspname , cur_relname ) ;
EmitErrorReport ( ) ;
/* this resets the PGXACT flags too */
AbortOutOfAnyTransaction ( ) ;
FlushErrorState ( ) ;
MemoryContextResetAndDeleteChildren ( PortalContext ) ;
/* restart our transaction for the following operations */
StartTransactionCommand ( ) ;
RESUME_INTERRUPTS ( ) ;
}
PG_END_TRY ( ) ;
/* We intentionally do not set did_vacuum here */
/* be tidy */
deleted2 :
if ( cur_datname )
pfree ( cur_datname ) ;
if ( cur_nspname )
pfree ( cur_nspname ) ;
if ( cur_relname )
pfree ( cur_relname ) ;
}
/*
* extract_autovac_opts
*
@ -2945,6 +3171,45 @@ autovac_report_activity(autovac_table *tab)
pgstat_report_activity ( STATE_RUNNING , activity ) ;
}
/*
* autovac_report_workitem
* Report to pgstat that autovacuum is processing a work item
*/
static void
autovac_report_workitem ( AutoVacuumWorkItem * workitem ,
const char * nspname , const char * relname )
{
char activity [ MAX_AUTOVAC_ACTIV_LEN + 12 + 2 ] ;
char blk [ 12 + 2 ] ;
int len ;
switch ( workitem - > avw_type )
{
case AVW_BRINSummarizeRange :
snprintf ( activity , MAX_AUTOVAC_ACTIV_LEN ,
" autovacuum: BRIN summarize " ) ;
break ;
}
/*
* Report the qualified name of the relation , and the block number if any
*/
len = strlen ( activity ) ;
if ( BlockNumberIsValid ( workitem - > avw_blockNumber ) )
snprintf ( blk , sizeof ( blk ) , " %u " , workitem - > avw_blockNumber ) ;
else
blk [ 0 ] = ' \0 ' ;
snprintf ( activity + len , MAX_AUTOVAC_ACTIV_LEN - len ,
" %s.%s%s " , nspname , relname , blk ) ;
/* Set statement_timestamp() to current time for pg_stat_activity */
SetCurrentStatementStartTimestamp ( ) ;
pgstat_report_activity ( STATE_RUNNING , activity ) ;
}
/*
* AutoVacuumingActive
* Check GUC vars and report whether the autovacuum process should be
@ -2958,6 +3223,113 @@ AutoVacuumingActive(void)
return true ;
}
/*
* Request one work item to the next autovacuum run processing our database .
*/
void
AutoVacuumRequestWork ( AutoVacuumWorkItemType type , Oid relationId ,
BlockNumber blkno )
{
AutovacWorkItems * workitems ;
dsa_pointer wi_ptr ;
AutoVacuumWorkItem * workitem ;
LWLockAcquire ( AutovacuumLock , LW_EXCLUSIVE ) ;
/*
* It may be useful to de - duplicate the list upon insertion . For the only
* currently existing caller , this is not necessary .
*/
/* First use in this process? Set up DSA */
if ( ! AutoVacuumDSA )
{
if ( ! AutoVacuumShmem - > av_dsa_handle )
{
/* autovacuum launcher not started; nothing can be done */
LWLockRelease ( AutovacuumLock ) ;
return ;
}
AutoVacuumDSA = dsa_attach ( AutoVacuumShmem - > av_dsa_handle ) ;
dsa_pin_mapping ( AutoVacuumDSA ) ;
}
/* First use overall? Allocate work items array */
if ( AutoVacuumShmem - > av_workitems = = InvalidDsaPointer )
{
int i ;
AutovacWorkItems * workitems ;
AutoVacuumShmem - > av_workitems =
dsa_allocate_extended ( AutoVacuumDSA ,
sizeof ( AutovacWorkItems ) +
NUM_WORKITEMS * sizeof ( AutoVacuumWorkItem ) ,
DSA_ALLOC_NO_OOM ) ;
/* if out of memory, silently disregard the request */
if ( AutoVacuumShmem - > av_workitems = = InvalidDsaPointer )
{
LWLockRelease ( AutovacuumLock ) ;
dsa_detach ( AutoVacuumDSA ) ;
AutoVacuumDSA = NULL ;
return ;
}
/* Initialize each array entry as a member of the free list */
workitems = dsa_get_address ( AutoVacuumDSA , AutoVacuumShmem - > av_workitems ) ;
workitems - > avs_usedItems = InvalidDsaPointer ;
workitems - > avs_freeItems = InvalidDsaPointer ;
for ( i = 0 ; i < NUM_WORKITEMS ; i + + )
{
/* XXX surely there is a simpler way to do this */
wi_ptr = AutoVacuumShmem - > av_workitems + sizeof ( AutovacWorkItems ) +
sizeof ( AutoVacuumWorkItem ) * i ;
workitem = ( AutoVacuumWorkItem * ) dsa_get_address ( AutoVacuumDSA , wi_ptr ) ;
workitem - > avw_type = 0 ;
workitem - > avw_database = InvalidOid ;
workitem - > avw_relation = InvalidOid ;
workitem - > avw_active = false ;
/* put this item in the free list */
workitem - > avw_next = workitems - > avs_freeItems ;
workitems - > avs_freeItems = wi_ptr ;
}
}
workitems = ( AutovacWorkItems * )
dsa_get_address ( AutoVacuumDSA , AutoVacuumShmem - > av_workitems ) ;
/* If array is full, disregard the request */
if ( workitems - > avs_freeItems = = InvalidDsaPointer )
{
LWLockRelease ( AutovacuumLock ) ;
dsa_detach ( AutoVacuumDSA ) ;
AutoVacuumDSA = NULL ;
return ;
}
/* remove workitem struct from free list ... */
wi_ptr = workitems - > avs_freeItems ;
remove_wi_from_list ( & workitems - > avs_freeItems , wi_ptr ) ;
/* ... initialize it ... */
workitem = dsa_get_address ( AutoVacuumDSA , wi_ptr ) ;
workitem - > avw_type = type ;
workitem - > avw_database = MyDatabaseId ;
workitem - > avw_relation = relationId ;
workitem - > avw_blockNumber = blkno ;
workitem - > avw_active = false ;
/* ... and put it on autovacuum's to-do list */
add_wi_to_list ( & workitems - > avs_usedItems , wi_ptr ) ;
LWLockRelease ( AutovacuumLock ) ;
dsa_detach ( AutoVacuumDSA ) ;
AutoVacuumDSA = NULL ;
}
/*
* autovac_init
* This is called at postmaster initialization .
@ -3079,3 +3451,59 @@ autovac_refresh_stats(void)
pgstat_clear_snapshot ( ) ;
}
/*
* Simplistic open - coded list implementation for objects stored in DSA .
* Each item is doubly linked , but we have no tail pointer , and the " prev "
* element of the first item is null , not the list .
*/
/*
* Remove a work item from the given list .
*/
static void
remove_wi_from_list ( dsa_pointer * list , dsa_pointer wi_ptr )
{
AutoVacuumWorkItem * workitem = dsa_get_address ( AutoVacuumDSA , wi_ptr ) ;
dsa_pointer next = workitem - > avw_next ;
dsa_pointer prev = workitem - > avw_prev ;
workitem - > avw_next = workitem - > avw_prev = InvalidDsaPointer ;
if ( next ! = InvalidDsaPointer )
{
workitem = dsa_get_address ( AutoVacuumDSA , next ) ;
workitem - > avw_prev = prev ;
}
if ( prev ! = InvalidDsaPointer )
{
workitem = dsa_get_address ( AutoVacuumDSA , prev ) ;
workitem - > avw_next = next ;
}
else
* list = next ;
}
/*
* Add a workitem to the given list
*/
static void
add_wi_to_list ( dsa_pointer * list , dsa_pointer wi_ptr )
{
if ( * list = = InvalidDsaPointer )
{
/* list is empty; item is now singleton */
* list = wi_ptr ;
}
else
{
AutoVacuumWorkItem * workitem = dsa_get_address ( AutoVacuumDSA , wi_ptr ) ;
AutoVacuumWorkItem * old = dsa_get_address ( AutoVacuumDSA , * list ) ;
/* Put item at head of list */
workitem - > avw_next = * list ;
old - > avw_prev = wi_ptr ;
* list = wi_ptr ;
}
}