GUC serialization for Autonomous Transaction

This commit is contained in:
jiang_jianyu
2020-08-25 21:36:32 +08:00
parent 4ba086877c
commit ecbb531378
2 changed files with 552 additions and 0 deletions

View File

@ -198,6 +198,18 @@
#define MAX_PASSWORD_ASSIGNED_CHARACTER 999
/* max length of password */
#define MAX_PASSWORD_LENGTH 999
/*
* Precision with which REAL type guc values are to be printed for GUC
* serialization.
*/
static const int REALTYPE_PRECISION = 17;
static const int TYPICAL_LEN_RANGE_OF_VALUE = 1000;
static const int MAX_DISPLAY_LEN_OF_BOOL = 5;
static const int TYPICAL_DISPLAY_LEN_OF_INT = 4;
static const int MAX_DISPLAY_LEN_OF_INT = 11;
static const int MAX_DISPLAY_LEN_OF_INT64 = 20;
static const int LEN_OF_REAL_EXCEPT_PRECISION = 8;
extern volatile int synchronous_commit;
extern volatile bool most_available_sync;
@ -17767,6 +17779,542 @@ ArrayType* GUCArrayReset(ArrayType* array)
return newarray;
}
/* GUC serialization */
static bool CanSkipGucvar(const struct config_generic* gconf);
static Size EstimateVariableSize(const struct config_generic* gconf);
static void DoSerialize(char** destptr, Size& maxbytes, const char* fmt, ...);
static void DoSerializeBinary(char** destptr, Size& maxbytes, const char* val, Size valsize);
static void SerializeVariable(char** destptr, Size& maxbytes, const struct config_generic* gconf);
static void InitializeOneGUCOption(struct config_generic& gconf);
static char* ReadGucstate(char** srcptr, const char* srcend);
static void ReadGucstateBinary(char** srcptr, const char* srcend, char* dest, Size size);
/*
* CanSkipGucvar:
* When serializing, determine whether to skip this GUC. When restoring, the
* negation of this test determines whether to restore the compiled-in default
* value before processing serialized values.
*
* A PGC_S_DEFAULT setting on the serialize side will typically match new
* postmaster children, but that can be false when got_SIGHUP == true and the
* pending configuration change modifies this setting. Nonetheless, we omit
* PGC_S_DEFAULT settings from serialization and make up for that by restoring
* defaults before applying serialized values.
*
* PGC_POSTMASTER variables always have the same value in every child of a
* particular postmaster. Most PGC_INTERNAL variables are compile-time
* constants; a few, like server_encoding and lc_ctype, are handled specially
* outside the serialize/restore procedure. Therefore, SerializeGUCState()
* never sends these, and RestoreGUCState() never changes them.
*/
static bool CanSkipGucvar(const struct config_generic* gconf)
{
return gconf->context == PGC_POSTMASTER ||
gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT ||
strcmp(gconf->name, "role") == 0;
}
/*
* EstimateVariableSize:
* Estimate max size for dumping the given GUC variable.
*/
static Size EstimateVariableSize(const struct config_generic* gconf)
{
Size size;
Size valsize = 0;
if (CanSkipGucvar(gconf)) {
return 0;
}
size = strlen(gconf->name) + 1;
/* Get the maximum display length of the GUC value. */
switch (gconf->vartype) {
case PGC_BOOL: {
valsize = MAX_DISPLAY_LEN_OF_BOOL;
break;
}
case PGC_INT: {
const struct config_int* conf = (const struct config_int*)gconf;
/*
* Instead of getting the exact display length, use max
* length. Also reduce the max length for typical ranges of
* small values. Maximum value is 2147483647, i.e. 10 chars.
* Include one byte for sign.
*/
if (Abs(*conf->variable) < TYPICAL_LEN_RANGE_OF_VALUE) {
valsize = TYPICAL_DISPLAY_LEN_OF_INT;
} else {
valsize = MAX_DISPLAY_LEN_OF_INT;
}
break;
}
case PGC_INT64: {
const struct config_int* conf = (const struct config_int*)gconf;
if (Abs(*conf->variable) < TYPICAL_LEN_RANGE_OF_VALUE) {
valsize = TYPICAL_DISPLAY_LEN_OF_INT;
} else {
valsize = MAX_DISPLAY_LEN_OF_INT64; /* Maximum value is 9,223,372,036,854,775,807, i.e. 19 chars. */
}
break;
}
case PGC_REAL: {
/*
* We are going to print it with %.17g. Account for sign,
* decimal point, and e+nnn notation. E.g.
* -3.9932904234000002e+110
*/
valsize = LEN_OF_REAL_EXCEPT_PRECISION + REALTYPE_PRECISION;
break;
}
case PGC_STRING: {
const struct config_string* conf = (const struct config_string*)gconf;
/*
* If the value is NULL, we transmit it as an empty string.
* Although this is not physically the same value, GUC
* generally treats a NULL the same as empty string.
*/
if (*conf->variable) {
valsize = strlen(*conf->variable);
} else {
valsize = 0;
}
break;
}
case PGC_ENUM: {
struct config_enum* conf = (struct config_enum*) gconf;
valsize = strlen(config_enum_lookup_by_value(conf, *conf->variable));
break;
}
default:
break;
}
/* Allow space for terminating zero-byte */
size = add_size(size, valsize + 1);
if (gconf->sourcefile) {
size = add_size(size, strlen(gconf->sourcefile));
}
/* Allow space for terminating zero-byte */
size = add_size(size, 1);
/* Include line whenever we include file. */
if (gconf->sourcefile && gconf->sourcefile[0]) {
size = add_size(size, sizeof(gconf->sourceline));
}
size = add_size(size, sizeof(gconf->source));
size = add_size(size, sizeof(gconf->scontext));
return size;
}
/*
* EstimateGUCStateSpace:
* Returns the size needed to store the GUC state for the current process
*/
Size EstimateGUCStateSpace(void)
{
Size size;
int i;
/* Add space reqd for saving the data size of the guc state */
size = sizeof(Size);
/* Add up the space needed for each GUC variable */
for (i = 0; i < u_sess->num_guc_variables; i++) {
size = add_size(size, EstimateVariableSize(u_sess->guc_variables[i]));
}
return size;
}
/*
* DoSerialize:
* Copies the formatted string into the destination. Moves ahead the
* destination pointer, and decrements the maxbytes by that many bytes. If
* maxbytes is not sufficient to copy the string, error out.
*/
static void DoSerialize(char** destptr, Size& maxbytes, const char* fmt, ...)
{
va_list vargs;
int nRet;
if (maxbytes == 0) {
elog(ERROR, "not enough space to serialize GUC state");
}
va_start(vargs, fmt);
nRet = vsnprintf_s(*destptr, maxbytes, maxbytes - 1, fmt, vargs);
securec_check_ss(nRet, "\0", "\0");
va_end(vargs);
/*
* Cater to portability hazards in the vsnprintf() return value just like
* appendPQExpBufferVA() does. Note that this requires an extra byte of
* slack at the end of the buffer. Since serialize_variable() ends with a
* do_serialize_binary() rather than a do_serialize(), we'll always have
* that slack; estimate_variable_size() need not add a byte for it.
*/
if (nRet < 0) {
/* Shouldn't happen. Better show errno description. */
elog(ERROR, "vsnprintf failed: %s with format string \"%s\"", strerror(nRet), fmt);
}
if (nRet >= static_cast<int>(maxbytes)) {
/* This shouldn't happen either, really. */
elog(ERROR, "not enough space to serialize GUC state");
}
/* Shift the destptr ahead of the null terminator */
*destptr += nRet + 1;
maxbytes -= static_cast<Size>(nRet) + 1;
}
/* Binary copy version of DoSerialize() */
static void DoSerializeBinary(char** destptr, Size& maxbytes, const char* val, Size valsize)
{
if (valsize > maxbytes) {
elog(ERROR, "not enough space to serialize GUC state");
}
errno_t rc = memcpy_s(*destptr, maxbytes, val, valsize);
securec_check(rc, "\0", "\0");
*destptr += valsize;
maxbytes -= valsize;
}
/*
* SerializeVariable:
* Dumps name, value and other information of a GUC variable into destptr.
*/
static void SerializeVariable(char** destptr, Size& maxbytes, const struct config_generic* gconf)
{
if (CanSkipGucvar(gconf)) {
return;
}
DoSerialize(destptr, maxbytes, "%s", gconf->name);
switch (gconf->vartype) {
case PGC_BOOL: {
const struct config_bool* conf = (const struct config_bool*)gconf;
DoSerialize(destptr, maxbytes, (*conf->variable ? "true" : "false"));
break;
}
case PGC_INT: {
const struct config_int* conf = (const struct config_int*)gconf;
DoSerialize(destptr, maxbytes, "%d", *conf->variable);
break;
}
case PGC_INT64: {
const struct config_int64* conf = (const struct config_int64*)gconf;
DoSerialize(destptr, maxbytes, "%ld", *conf->variable);
break;
}
case PGC_REAL: {
const struct config_real* conf = (const struct config_real*)gconf;
DoSerialize(destptr, maxbytes, "%.*e", REALTYPE_PRECISION, *conf->variable);
break;
}
case PGC_STRING:{
const struct config_string* conf = (const struct config_string*)gconf;
DoSerialize(destptr, maxbytes, "%s", *conf->variable ? *conf->variable : "");
break;
}
case PGC_ENUM:{
struct config_enum* conf = (struct config_enum*)gconf;
DoSerialize(destptr, maxbytes, "%s", config_enum_lookup_by_value(conf, *conf->variable));
break;
}
default:
break;
}
DoSerialize(destptr, maxbytes, "%s", (gconf->sourcefile ? gconf->sourcefile : ""));
if (gconf->sourcefile) {
DoSerializeBinary(destptr, maxbytes, reinterpret_cast<const char*>(&gconf->sourceline),
sizeof(gconf->sourceline));
}
DoSerializeBinary(destptr, maxbytes, reinterpret_cast<const char*>(&gconf->source), sizeof(gconf->source));
DoSerializeBinary(destptr, maxbytes, reinterpret_cast<const char*>(&gconf->scontext), sizeof(gconf->scontext));
}
/*
* SerializeGUCState:
* Dumps the complete GUC state onto the memory location at startAddress.
*/
void SerializeGUCState(Size maxsize, char* startAddress)
{
char *curptr;
Size actualSize;
Size bytesLeft;
int i;
/* Reserve space for saving the actual size of the guc state */
Assert(maxsize > sizeof(actualSize));
curptr = startAddress + sizeof(actualSize);
bytesLeft = maxsize - sizeof(actualSize);
for (i = 0; i < u_sess->num_guc_variables; i++) {
SerializeVariable(&curptr, bytesLeft, u_sess->guc_variables[i]);
}
/* Store actual size without assuming alignment of startAddress. */
actualSize = maxsize - bytesLeft - sizeof(actualSize);
errno_t rc = memcpy_s(startAddress, maxsize, &actualSize, sizeof(actualSize));
securec_check(rc, "\0", "\0");
}
/*
* Initialize one GUC option variable to its compiled-in default.
*
* Note: the reason for calling check_hooks is not that we think the boot_val
* might fail, but that the hooks might wish to compute an "extra" struct.
*/
static void InitializeOneGUCOption(struct config_generic& gconf)
{
gconf.status = 0;
gconf.source = PGC_S_DEFAULT;
gconf.reset_source = PGC_S_DEFAULT;
gconf.scontext = PGC_INTERNAL;
gconf.reset_scontext = PGC_INTERNAL;
gconf.stack = NULL;
gconf.extra = NULL;
gconf.sourcefile = NULL;
gconf.sourceline = 0;
switch (gconf.vartype) {
case PGC_BOOL: {
struct config_bool *conf = (struct config_bool*)&gconf;
bool newval = conf->boot_val;
void* extra = NULL;
if (!call_bool_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) {
elog(FATAL, "failed to initialize %s to %d", conf->gen.name, static_cast<int>(newval));
}
if (conf->assign_hook) {
(*conf->assign_hook) (newval, extra);
}
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
break;
}
case PGC_INT: {
struct config_int* conf = (struct config_int*)&gconf;
int newval = conf->boot_val;
void* extra = NULL;
Assert(newval >= conf->min);
Assert(newval <= conf->max);
if (!call_int_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) {
elog(FATAL, "failed to initialize %s to %d", conf->gen.name, newval);
}
if (conf->assign_hook) {
(*conf->assign_hook) (newval, extra);
}
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
break;
}
case PGC_INT64: {
struct config_int64* conf = (struct config_int64*)&gconf;
int64 newval = conf->boot_val;
void* extra = NULL;
Assert(newval >= conf->min);
Assert(newval <= conf->max);
if (!call_int64_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) {
elog(FATAL, "failed to initialize %s to %ld", conf->gen.name, newval);
}
if (conf->assign_hook) {
(*conf->assign_hook) (newval, extra);
}
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
break;
}
case PGC_REAL: {
struct config_real* conf = (struct config_real*)&gconf;
double newval = conf->boot_val;
void* extra = NULL;
Assert(newval >= conf->min);
Assert(newval <= conf->max);
if (!call_real_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) {
elog(FATAL, "failed to initialize %s to %g", conf->gen.name, newval);
}
if (conf->assign_hook) {
(*conf->assign_hook) (newval, extra);
}
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
break;
}
case PGC_STRING: {
struct config_string* conf = (struct config_string*)&gconf;
char* newval;
void* extra = NULL;
/* non-NULL boot_val must always get strdup'd */
if (conf->boot_val != NULL) {
newval = guc_strdup(FATAL, conf->boot_val);
} else {
newval = NULL;
}
if (!call_string_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) {
elog(FATAL, "failed to initialize %s to \"%s\"", conf->gen.name, newval ? newval : "");
}
if (conf->assign_hook) {
(*conf->assign_hook) (newval, extra);
}
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
break;
}
case PGC_ENUM: {
struct config_enum *conf = (struct config_enum*)&gconf;
int newval = conf->boot_val;
void* extra = NULL;
if (!call_enum_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) {
elog(FATAL, "failed to initialize %s to %d", conf->gen.name, newval);
}
if (conf->assign_hook) {
(*conf->assign_hook) (newval, extra);
}
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
break;
}
default:
break;
}
}
/*
* ReadGucstate:
* Actually it does not read anything, just returns the srcptr. But it does
* move the srcptr past the terminating zero byte, so that the caller is ready
* to read the next string.
*/
static char* ReadGucstate(char** srcptr, const char* srcend)
{
char* retptr = *srcptr;
char* ptr;
if (*srcptr >= srcend) {
elog(ERROR, "incomplete GUC state");
}
/* The string variables are all null terminated */
for (ptr = *srcptr; ptr < srcend && *ptr != '\0'; ptr++) {}
if (ptr > srcend) {
elog(ERROR, "could not find null terminator in GUC state");
}
/* Set the new position to the byte following the terminating NUL */
*srcptr = ptr + 1;
return retptr;
}
/* Binary read version of ReadGucstate(). Copies into dest */
static void ReadGucstateBinary(char** srcptr, const char* srcend, char* dest, Size size)
{
if (*srcptr + size > srcend) {
elog(ERROR, "incomplete GUC state");
}
errno_t rc = memcpy_s(dest, size, *srcptr, size);
securec_check(rc, "\0", "\0");
*srcptr += size;
}
/*
* RestoreGUCState:
* Reads the GUC state at the specified address and updates the GUCs with the
* values read from the GUC state.
*/
void RestoreGUCState(char* gucstate)
{
char* varname;
char* varvalue;
char* varsourcefile;
int varsourceline;
GucSource varsource;
GucContext varscontext;
char* srcptr = gucstate;
char* srcend;
Size len;
int i;
/* See comment at can_skip_gucvar(). */
for (i = 0; i < u_sess->num_guc_variables; i++) {
if (!CanSkipGucvar(u_sess->guc_variables[i])) {
InitializeOneGUCOption(*u_sess->guc_variables[i]);
}
}
/* First item is the length of the subsequent data */
errno_t rc = memcpy_s(&len, sizeof(len), gucstate, sizeof(len));
securec_check(rc, "\0", "\0");
srcptr += sizeof(len);
srcend = srcptr + len;
while (srcptr < srcend) {
int result;
varname = ReadGucstate(&srcptr, srcend);
varvalue = ReadGucstate(&srcptr, srcend);
varsourcefile = ReadGucstate(&srcptr, srcend);
if (varsourcefile[0]) {
ReadGucstateBinary(&srcptr, srcend,
reinterpret_cast<char* >(&varsourceline), sizeof(varsourceline));
} else {
varsourceline = 0;
}
ReadGucstateBinary(&srcptr, srcend,
reinterpret_cast<char* >(&varsource), sizeof(varsource));
ReadGucstateBinary(&srcptr, srcend,
reinterpret_cast<char* >(&varscontext), sizeof(varscontext));
result = set_config_option(varname, varvalue, varscontext, varsource,
GUC_ACTION_SET, true, ERROR, true);
if (result <= 0) {
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("parameter \"%s\" could not be set", varname)));
}
if (varsourcefile[0]) {
set_config_sourcefile(varname, varsourcefile, varsourceline);
}
}
}
/*
* Validate a proposed option setting for GUCArrayAdd/Delete/Reset.
*

View File

@ -266,6 +266,10 @@ extern ArrayType* GUCArrayAdd(ArrayType* array, const char* name, const char* va
extern ArrayType* GUCArrayDelete(ArrayType* array, const char* name);
extern ArrayType* GUCArrayReset(ArrayType* array);
extern Size EstimateGUCStateSpace(void);
extern void SerializeGUCState(Size maxsize, char *start_address);
extern void RestoreGUCState(char *gucstate);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
extern void read_nondefault_variables(void);