1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00
Files
postgres/contrib/pg_stash_advice/pg_stash_advice.c
Peter Eisentraut c90c9678e5 Message style fixes
Change DETAIL messages to conform to the style guide by capitalizing
the first word of sentences and ending sentences with a period.

Author: Peter Smith <peter.b.smith@fujitsu.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: vignesh C <vignesh21.gmail.com>
Reviewed-by: Xiaopeng Wang <wxp_728.163.com>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Discussion: https://www.postgresql.org/message-id/flat/CAHut%2BPszSntkUgN%2BQa9matGY6MLEoFGSuVbuKDgnnTdZ7YPRwg%40mail.gmail.com
2026-07-21 15:32:56 +02:00

777 lines
22 KiB
C

/*-------------------------------------------------------------------------
*
* pg_stash_advice.c
* core infrastructure for pg_stash_advice contrib module
*
* Copyright (c) 2016-2026, PostgreSQL Global Development Group
*
* contrib/pg_stash_advice/pg_stash_advice.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "common/hashfn.h"
#include "common/string.h"
#include "miscadmin.h"
#include "nodes/queryjumble.h"
#include "pg_plan_advice.h"
#include "pg_stash_advice.h"
#include "postmaster/bgworker.h"
#include "storage/dsm_registry.h"
#include "utils/guc.h"
#include "utils/memutils.h"
PG_MODULE_MAGIC_EXT(
.name = "pg_stash_advice",
.version = PG_VERSION
);
/* Shared memory hash table parameters */
static dshash_parameters pgsa_stash_dshash_parameters = {
NAMEDATALEN,
sizeof(pgsa_stash),
dshash_strcmp,
dshash_strhash,
dshash_strcpy,
LWTRANCHE_INVALID /* gets set at runtime */
};
static dshash_parameters pgsa_entry_dshash_parameters = {
sizeof(pgsa_entry_key),
sizeof(pgsa_entry),
dshash_memcmp,
dshash_memhash,
dshash_memcpy,
LWTRANCHE_INVALID /* gets set at runtime */
};
/* GUC variables */
static char *pg_stash_advice_stash_name = "";
bool pg_stash_advice_persist = true;
int pg_stash_advice_persist_interval = 30;
/* Shared memory pointers */
pgsa_shared_state *pgsa_state;
dsa_area *pgsa_dsa_area;
dshash_table *pgsa_stash_dshash;
dshash_table *pgsa_entry_dshash;
/* Other global variables */
static MemoryContext pg_stash_advice_mcxt;
/* Function prototypes */
static char *pgsa_advisor(PlannerGlobal *glob,
Query *parse,
const char *query_string,
int cursorOptions,
ExplainState *es);
static bool pgsa_check_stash_name_guc(char **newval, void **extra,
GucSource source);
static void pgsa_init_shared_state(void *ptr, void *arg);
static bool pgsa_is_identifier(char *str);
/* Stash name -> stash ID hash table */
#define SH_PREFIX pgsa_stash_name_table
#define SH_ELEMENT_TYPE pgsa_stash_name
#define SH_KEY_TYPE uint64
#define SH_KEY pgsa_stash_id
#define SH_HASH_KEY(tb, key) hash_bytes((const unsigned char *) &(key), sizeof(uint64))
#define SH_EQUAL(tb, a, b) (a == b)
#define SH_SCOPE extern
#define SH_DEFINE
#include "lib/simplehash.h"
/*
* Initialize this module.
*/
void
_PG_init(void)
{
void (*add_advisor_fn) (pg_plan_advice_advisor_hook hook);
/* If compute_query_id = 'auto', we would like query IDs. */
EnableQueryId();
/* Define our GUCs. */
if (process_shared_preload_libraries_in_progress)
DefineCustomBoolVariable("pg_stash_advice.persist",
"Save and restore advice stash contents across restarts.",
NULL,
&pg_stash_advice_persist,
true,
PGC_POSTMASTER,
0,
NULL,
NULL,
NULL);
else
pg_stash_advice_persist = false;
DefineCustomIntVariable("pg_stash_advice.persist_interval",
"Interval between advice stash saves, in seconds.",
NULL,
&pg_stash_advice_persist_interval,
30,
0,
3600,
PGC_SIGHUP,
GUC_UNIT_S,
NULL,
NULL,
NULL);
DefineCustomStringVariable("pg_stash_advice.stash_name",
"Name of the advice stash to be used in this session.",
NULL,
&pg_stash_advice_stash_name,
"",
PGC_USERSET,
0,
pgsa_check_stash_name_guc,
NULL,
NULL);
MarkGUCPrefixReserved("pg_stash_advice");
/* Start the background worker for persistence, if enabled. */
if (pg_stash_advice_persist)
pgsa_start_worker();
/* Tell pg_plan_advice that we want to provide advice strings. */
add_advisor_fn =
load_external_function("pg_plan_advice", "pg_plan_advice_add_advisor",
true, NULL);
(*add_advisor_fn) (pgsa_advisor);
}
/*
* Get the advice string that has been configured for this query, if any,
* and return it. Otherwise, return NULL.
*/
static char *
pgsa_advisor(PlannerGlobal *glob, Query *parse,
const char *query_string, int cursorOptions,
ExplainState *es)
{
pgsa_entry_key key;
pgsa_entry *entry;
char *advice_string;
uint64 stash_id;
/*
* Exit quickly if the stash name is empty or there's no query ID.
*/
if (pg_stash_advice_stash_name[0] == '\0' || parse->queryId == 0)
return NULL;
/* Attach to dynamic shared memory if not already done. */
if (unlikely(pgsa_entry_dshash == NULL))
pgsa_attach();
/* If stash data is still being restored from disk, ignore. */
if (pg_atomic_unlocked_test_flag(&pgsa_state->stashes_ready))
return NULL;
/*
* Translate pg_stash_advice.stash_name to an integer ID.
*
* pgsa_check_stash_name_guc() has already validated the advice stash
* name, so we don't need to call pgsa_check_stash_name() here.
*/
stash_id = pgsa_lookup_stash_id(pg_stash_advice_stash_name);
if (stash_id == 0)
return NULL;
/*
* Look up the advice string for the given stash ID + query ID.
*
* If we find an advice string, we copy it into the current memory
* context, presumably short-lived, so that we can release the lock on the
* dshash entry. pg_plan_advice only needs the value to remain allocated
* long enough for it to be parsed, so this should be good enough.
*/
memset(&key, 0, sizeof(pgsa_entry_key));
key.pgsa_stash_id = stash_id;
key.queryId = parse->queryId;
entry = dshash_find(pgsa_entry_dshash, &key, false);
if (entry == NULL)
return NULL;
if (entry->advice_string == InvalidDsaPointer)
advice_string = NULL;
else
advice_string = pstrdup(dsa_get_address(pgsa_dsa_area,
entry->advice_string));
dshash_release_lock(pgsa_entry_dshash, entry);
/* If we found an advice string, emit a debug message. */
if (advice_string != NULL)
elog(DEBUG2, "supplying automatic advice for stash \"%s\", query ID %" PRId64 ": %s",
pg_stash_advice_stash_name, key.queryId, advice_string);
return advice_string;
}
/*
* Attach to various structures in dynamic shared memory.
*
* This function is designed to be resilient against errors. That is, if it
* fails partway through, it should be possible to call it again, repeat no
* work already completed, and potentially succeed or at least get further if
* whatever caused the previous failure has been corrected.
*/
void
pgsa_attach(void)
{
bool found;
MemoryContext oldcontext;
/*
* Create a memory context to make sure that any control structures
* allocated in local memory are sufficiently persistent.
*/
if (pg_stash_advice_mcxt == NULL)
pg_stash_advice_mcxt = AllocSetContextCreate(TopMemoryContext,
"pg_stash_advice",
ALLOCSET_DEFAULT_SIZES);
oldcontext = MemoryContextSwitchTo(pg_stash_advice_mcxt);
/* Attach to the fixed-size state object if not already done. */
if (pgsa_state == NULL)
pgsa_state = GetNamedDSMSegment("pg_stash_advice",
sizeof(pgsa_shared_state),
pgsa_init_shared_state,
&found, NULL);
/* Attach to the DSA area if not already done. */
if (pgsa_dsa_area == NULL)
{
dsa_handle area_handle;
LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
area_handle = pgsa_state->area;
if (area_handle == DSA_HANDLE_INVALID)
{
pgsa_dsa_area = dsa_create(pgsa_state->dsa_tranche);
dsa_pin(pgsa_dsa_area);
pgsa_state->area = dsa_get_handle(pgsa_dsa_area);
LWLockRelease(&pgsa_state->lock);
}
else
{
LWLockRelease(&pgsa_state->lock);
pgsa_dsa_area = dsa_attach(area_handle);
}
dsa_pin_mapping(pgsa_dsa_area);
}
/* Attach to the stash_name->stash_id hash table if not already done. */
if (pgsa_stash_dshash == NULL)
{
dshash_table_handle stash_handle;
LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
pgsa_stash_dshash_parameters.tranche_id = pgsa_state->stash_tranche;
stash_handle = pgsa_state->stash_hash;
if (stash_handle == DSHASH_HANDLE_INVALID)
{
pgsa_stash_dshash = dshash_create(pgsa_dsa_area,
&pgsa_stash_dshash_parameters,
NULL);
pgsa_state->stash_hash =
dshash_get_hash_table_handle(pgsa_stash_dshash);
LWLockRelease(&pgsa_state->lock);
}
else
{
LWLockRelease(&pgsa_state->lock);
pgsa_stash_dshash = dshash_attach(pgsa_dsa_area,
&pgsa_stash_dshash_parameters,
stash_handle, NULL);
}
}
/* Attach to the entry hash table if not already done. */
if (pgsa_entry_dshash == NULL)
{
dshash_table_handle entry_handle;
LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
pgsa_entry_dshash_parameters.tranche_id = pgsa_state->entry_tranche;
entry_handle = pgsa_state->entry_hash;
if (entry_handle == DSHASH_HANDLE_INVALID)
{
pgsa_entry_dshash = dshash_create(pgsa_dsa_area,
&pgsa_entry_dshash_parameters,
NULL);
pgsa_state->entry_hash =
dshash_get_hash_table_handle(pgsa_entry_dshash);
LWLockRelease(&pgsa_state->lock);
}
else
{
LWLockRelease(&pgsa_state->lock);
pgsa_entry_dshash = dshash_attach(pgsa_dsa_area,
&pgsa_entry_dshash_parameters,
entry_handle, NULL);
}
}
/* Restore previous memory context. */
MemoryContextSwitchTo(oldcontext);
}
/*
* Error out if the stashes have not been loaded from disk yet.
*/
void
pgsa_check_lockout(void)
{
if (pg_atomic_unlocked_test_flag(&pgsa_state->stashes_ready))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("stash modifications are not allowed because \"%s\" has not been loaded yet",
PGSA_DUMP_FILE)));
}
/*
* Check whether an advice stash name is legal, and signal an error if not.
*
* Keep this in sync with pgsa_check_stash_name_guc, below.
*/
void
pgsa_check_stash_name(char *stash_name)
{
/* Reject empty advice stash name. */
if (stash_name[0] == '\0')
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash name may not be zero length"));
/* Reject overlong advice stash names. */
if (strlen(stash_name) + 1 > NAMEDATALEN)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash names may not be longer than %d bytes",
NAMEDATALEN - 1));
/*
* Reject non-ASCII advice stash names, since advice stashes are visible
* across all databases and the encodings of those databases might differ.
*/
if (!pg_is_ascii(stash_name))
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash name must not contain non-ASCII characters"));
/*
* Reject things that do not look like identifiers, since the ability to
* create an advice stash with non-printable characters or weird symbols
* in the name is not likely to be useful to anyone.
*/
if (!pgsa_is_identifier(stash_name))
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash name must begin with a letter or underscore and contain only letters, digits, and underscores"));
}
/*
* As above, but for the GUC check_hook. We allow the empty string here,
* though, as equivalent to disabling the feature.
*/
static bool
pgsa_check_stash_name_guc(char **newval, void **extra, GucSource source)
{
char *stash_name = *newval;
/* Reject overlong advice stash names. */
if (strlen(stash_name) + 1 > NAMEDATALEN)
{
GUC_check_errcode(ERRCODE_INVALID_PARAMETER_VALUE);
GUC_check_errdetail("Advice stash names may not be longer than %d bytes.",
NAMEDATALEN - 1);
return false;
}
/*
* Reject non-ASCII advice stash names, since advice stashes are visible
* across all databases and the encodings of those databases might differ.
*/
if (!pg_is_ascii(stash_name))
{
GUC_check_errcode(ERRCODE_INVALID_PARAMETER_VALUE);
GUC_check_errdetail("Advice stash name must not contain non-ASCII characters.");
return false;
}
/*
* Reject things that do not look like identifiers, since the ability to
* create an advice stash with non-printable characters or weird symbols
* in the name is not likely to be useful to anyone.
*/
if (!pgsa_is_identifier(stash_name))
{
GUC_check_errcode(ERRCODE_INVALID_PARAMETER_VALUE);
GUC_check_errdetail("Advice stash name must begin with a letter or underscore and contain only letters, digits, and underscores.");
return false;
}
return true;
}
/*
* Create an advice stash.
*/
void
pgsa_create_stash(char *stash_name)
{
pgsa_stash *stash;
bool found;
Assert(LWLockHeldByMeInMode(&pgsa_state->lock, LW_EXCLUSIVE));
/* Create a stash with this name, unless one already exists. */
stash = dshash_find_or_insert(pgsa_stash_dshash, stash_name, &found);
if (found)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash \"%s\" already exists", stash_name));
stash->pgsa_stash_id = pgsa_state->next_stash_id++;
dshash_release_lock(pgsa_stash_dshash, stash);
/* Bump change count. */
pg_atomic_add_fetch_u64(&pgsa_state->change_count, 1);
}
/*
* Remove any stored advice string for the given advice stash and query ID.
*/
void
pgsa_clear_advice_string(char *stash_name, int64 queryId)
{
pgsa_entry *entry;
pgsa_entry_key key;
uint64 stash_id;
dsa_pointer old_dp;
Assert(LWLockHeldByMe(&pgsa_state->lock));
/* Translate the stash name to an integer ID. */
if ((stash_id = pgsa_lookup_stash_id(stash_name)) == 0)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash \"%s\" does not exist", stash_name));
/*
* Look for an existing entry, and free it. But, be sure to save the
* pointer to the associated advice string, if any.
*/
memset(&key, 0, sizeof(pgsa_entry_key));
key.pgsa_stash_id = stash_id;
key.queryId = queryId;
entry = dshash_find(pgsa_entry_dshash, &key, true);
if (entry == NULL)
old_dp = InvalidDsaPointer;
else
{
old_dp = entry->advice_string;
dshash_delete_entry(pgsa_entry_dshash, entry);
}
/* Now we free the advice string as well, if there was one. */
if (old_dp != InvalidDsaPointer)
dsa_free(pgsa_dsa_area, old_dp);
/* Bump change count. */
pg_atomic_add_fetch_u64(&pgsa_state->change_count, 1);
}
/*
* Drop an advice stash.
*/
void
pgsa_drop_stash(char *stash_name)
{
pgsa_entry *entry;
pgsa_stash *stash;
dshash_seq_status iterator;
uint64 stash_id;
Assert(LWLockHeldByMeInMode(&pgsa_state->lock, LW_EXCLUSIVE));
/* Remove the entry for this advice stash. */
stash = dshash_find(pgsa_stash_dshash, stash_name, true);
if (stash == NULL)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash \"%s\" does not exist", stash_name));
stash_id = stash->pgsa_stash_id;
dshash_delete_entry(pgsa_stash_dshash, stash);
/*
* Now remove all the entries. Since pgsa_state->lock must be held at
* least in shared mode to insert entries into pgsa_entry_dshash, it
* doesn't matter whether we do this before or after deleting the entry
* from pgsa_stash_dshash.
*/
dshash_seq_init(&iterator, pgsa_entry_dshash, true);
while ((entry = dshash_seq_next(&iterator)) != NULL)
{
if (stash_id == entry->key.pgsa_stash_id)
{
if (entry->advice_string != InvalidDsaPointer)
dsa_free(pgsa_dsa_area, entry->advice_string);
dshash_delete_current(&iterator);
}
}
dshash_seq_term(&iterator);
/* Bump change count. */
pg_atomic_add_fetch_u64(&pgsa_state->change_count, 1);
}
/*
* Remove all stashes and entries from shared memory.
*
* This is intended to be called before reloading from a dump file, so that
* a failed previous attempt doesn't leave stale data behind.
*/
void
pgsa_reset_all_stashes(void)
{
dshash_seq_status iter;
pgsa_entry *entry;
Assert(LWLockHeldByMeInMode(&pgsa_state->lock, LW_EXCLUSIVE));
/* Remove all stashes. */
dshash_seq_init(&iter, pgsa_stash_dshash, true);
while (dshash_seq_next(&iter) != NULL)
dshash_delete_current(&iter);
dshash_seq_term(&iter);
/* Remove all entries. */
dshash_seq_init(&iter, pgsa_entry_dshash, true);
while ((entry = dshash_seq_next(&iter)) != NULL)
{
if (entry->advice_string != InvalidDsaPointer)
dsa_free(pgsa_dsa_area, entry->advice_string);
dshash_delete_current(&iter);
}
dshash_seq_term(&iter);
/* Reset the stash ID counter. */
pgsa_state->next_stash_id = UINT64CONST(1);
}
/*
* Initialize shared state when first created.
*/
static void
pgsa_init_shared_state(void *ptr, void *arg)
{
pgsa_shared_state *state = (pgsa_shared_state *) ptr;
LWLockInitialize(&state->lock,
LWLockNewTrancheId("pg_stash_advice_lock"));
state->dsa_tranche = LWLockNewTrancheId("pg_stash_advice_dsa");
state->stash_tranche = LWLockNewTrancheId("pg_stash_advice_stash");
state->entry_tranche = LWLockNewTrancheId("pg_stash_advice_entry");
state->next_stash_id = UINT64CONST(1);
state->area = DSA_HANDLE_INVALID;
state->stash_hash = DSHASH_HANDLE_INVALID;
state->entry_hash = DSHASH_HANDLE_INVALID;
state->bgworker_pid = InvalidPid;
pg_atomic_init_flag(&state->stashes_ready);
pg_atomic_init_u64(&state->change_count, 0);
/*
* If this module was loaded via shared_preload_libraries, then
* pg_stash_advice_persist is a GUC variable. If it's true, that means
* that we should lock out manual stash modifications until the dump file
* has been successfully loaded. If it's false, there's nothing to load,
* so we set stashes_ready immediately.
*
* If this module was not loaded via shared_preload_libraries, then
* pg_stash_advice_persist is not a GUC variable, but it will be false,
* which leads to the correct behavior.
*/
if (!pg_stash_advice_persist)
pg_atomic_test_set_flag(&state->stashes_ready);
}
/*
* Check whether a string looks like a valid identifier. It must contain only
* ASCII identifier characters, and must not begin with a digit.
*/
static bool
pgsa_is_identifier(char *str)
{
if (*str >= '0' && *str <= '9')
return false;
while (*str != '\0')
{
char c = *str++;
if ((c < '0' || c > '9') && (c < 'a' || c > 'z') &&
(c < 'A' || c > 'Z') && c != '_')
return false;
}
return true;
}
/*
* Look up the integer ID that corresponds to the given stash name.
*
* Returns 0 if no such stash exists.
*/
uint64
pgsa_lookup_stash_id(char *stash_name)
{
pgsa_stash *stash;
uint64 stash_id;
/* Search the shared hash table. */
stash = dshash_find(pgsa_stash_dshash, stash_name, false);
if (stash == NULL)
return 0;
stash_id = stash->pgsa_stash_id;
dshash_release_lock(pgsa_stash_dshash, stash);
return stash_id;
}
/*
* Store a new or updated advice string for the given advice stash and query ID.
*/
void
pgsa_set_advice_string(char *stash_name, int64 queryId, char *advice_string)
{
pgsa_entry *entry;
bool found;
pgsa_entry_key key;
uint64 stash_id;
dsa_pointer new_dp;
dsa_pointer old_dp;
/*
* The caller must hold our lock, at least in shared mode. This is
* important for two reasons.
*
* First, it holds off interrupts, so that we can't bail out of this code
* after allocating DSA memory for the advice string and before storing
* the resulting pointer somewhere that others can find it.
*
* Second, we need to avoid a race against pgsa_drop_stash(). That
* function removes a stash_name->stash_id mapping and all the entries for
* that stash_id. Without the lock, there's a race condition no matter
* which of those things it does first, because as soon as we've looked up
* the stash ID, that whole function can execute before we do the rest of
* our work, which would result in us adding an entry for a stash that no
* longer exists.
*/
Assert(LWLockHeldByMe(&pgsa_state->lock));
/* Look up the stash ID. */
if ((stash_id = pgsa_lookup_stash_id(stash_name)) == 0)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("advice stash \"%s\" does not exist", stash_name));
/* Allocate space for the advice string. */
new_dp = dsa_allocate(pgsa_dsa_area, strlen(advice_string) + 1);
strcpy(dsa_get_address(pgsa_dsa_area, new_dp), advice_string);
/* Attempt to insert an entry into the hash table. */
memset(&key, 0, sizeof(pgsa_entry_key));
key.pgsa_stash_id = stash_id;
key.queryId = queryId;
entry = dshash_find_or_insert_extended(pgsa_entry_dshash, &key, &found,
DSHASH_INSERT_NO_OOM);
/*
* If it didn't work, bail out, being careful to free the shared memory
* we've already allocated before, since error cleanup will not do so.
*/
if (entry == NULL)
{
dsa_free(pgsa_dsa_area, new_dp);
ereport(ERROR,
errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory"),
errdetail("Could not insert advice string into shared hash table."));
}
/* Update the entry and release the lock. */
old_dp = found ? entry->advice_string : InvalidDsaPointer;
entry->advice_string = new_dp;
dshash_release_lock(pgsa_entry_dshash, entry);
/*
* We're not safe from leaks yet!
*
* There's now a pointer to new_dp in the entry that we just updated, but
* that means that there's no longer anything pointing to old_dp.
*/
if (DsaPointerIsValid(old_dp))
dsa_free(pgsa_dsa_area, old_dp);
/* Bump change count. */
pg_atomic_add_fetch_u64(&pgsa_state->change_count, 1);
}
/*
* Start our worker process.
*/
void
pgsa_start_worker(void)
{
BackgroundWorker worker = {0};
BackgroundWorkerHandle *handle;
BgwHandleStatus status;
pid_t pid;
worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
worker.bgw_start_time = BgWorkerStart_ConsistentState;
worker.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
strcpy(worker.bgw_library_name, "pg_stash_advice");
strcpy(worker.bgw_function_name, "pg_stash_advice_worker_main");
strcpy(worker.bgw_name, "pg_stash_advice worker");
strcpy(worker.bgw_type, "pg_stash_advice worker");
/*
* If process_shared_preload_libraries_in_progress = true, we may be in
* the postmaster, in which case this will really register the worker, or
* we may be in a child process in an EXEC_BACKEND build, in which case it
* will silently do nothing (which is the correct behavior).
*/
if (process_shared_preload_libraries_in_progress)
{
RegisterBackgroundWorker(&worker);
return;
}
/*
* If process_shared_preload_libraries_in_progress = false, we're being
* asked to start the worker after system startup time. In other words,
* unless this is single-user mode, we're not in the postmaster, so we
* should use RegisterDynamicBackgroundWorker and then wait for startup to
* complete. (If we do happen to be in single-user mode, this will error
* out, which is fine.)
*/
worker.bgw_notify_pid = MyProcPid;
if (!RegisterDynamicBackgroundWorker(&worker, &handle))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("could not register background process"),
errhint("You may need to increase \"max_worker_processes\".")));
status = WaitForBackgroundWorkerStartup(handle, &pid);
if (status != BGWH_STARTED)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("could not start background process"),
errhint("More details may be available in the server log.")));
}