/*********************************************************************************************************************************** Determine if the WAL process list has become large enough to drop ***********************************************************************************************************************************/ static bool archivePushDrop(const String *walPath, const StringList *const processList) { FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_PARAM(STRING, walPath); FUNCTION_LOG_PARAM(STRING_LIST, processList); FUNCTION_LOG_END(); const uint64_t queueMax = cfgOptionUInt64(cfgOptArchivePushQueueMax); uint64_t queueSize = 0; bool result = false; for (unsigned int processIdx = 0; processIdx < strLstSize(processList); processIdx++) { queueSize += storageInfoNP( storagePg(), strNewFmt("%s/%s", strPtr(walPath), strPtr(strLstGet(processList, processIdx)))).size; if (queueSize > queueMax) { result = true; break; } } FUNCTION_LOG_RETURN(BOOL, result); }
/*********************************************************************************************************************************** Get the list of WAL files ready to be pushed according to PostgreSQL ***********************************************************************************************************************************/ static StringList * archivePushReadyList(const String *walPath) { FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_PARAM(STRING, walPath); FUNCTION_LOG_END(); ASSERT(walPath != NULL); StringList *result = NULL; MEM_CONTEXT_TEMP_BEGIN() { result = strLstNew(); // Read the ready files from the archive_status directory StringList *readyListRaw = strLstSort( storageListP( storagePg(), strNewFmt("%s/" PG_PATH_ARCHIVE_STATUS, strPtr(walPath)), .expression = STRDEF("\\" STATUS_EXT_READY "$"), .errorOnMissing = true), sortOrderAsc); for (unsigned int readyIdx = 0; readyIdx < strLstSize(readyListRaw); readyIdx++) { strLstAdd( result, strSubN(strLstGet(readyListRaw, readyIdx), 0, strSize(strLstGet(readyListRaw, readyIdx)) - STATUS_EXT_READY_SIZE)); } strLstMove(result, MEM_CONTEXT_OLD()); } MEM_CONTEXT_TEMP_END(); FUNCTION_LOG_RETURN(STRING_LIST, result); }
/*********************************************************************************************************************************** Get option values from another process ***********************************************************************************************************************************/ VariantList * configProtocolOption(ProtocolClient *client, const VariantList *paramList) { FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, client); FUNCTION_LOG_PARAM(VARIANT_LIST, paramList); FUNCTION_LOG_END(); VariantList *result = NULL; MEM_CONTEXT_TEMP_BEGIN() { ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_CONFIG_OPTION_STR); for (unsigned int paramIdx = 0; paramIdx < varLstSize(paramList); paramIdx++) protocolCommandParamAdd(command, varLstGet(paramList, paramIdx)); memContextSwitch(MEM_CONTEXT_OLD()); result = varVarLst(protocolClientExecute(client, command, true)); memContextSwitch(MEM_CONTEXT_TEMP()); } MEM_CONTEXT_TEMP_END(); FUNCTION_LOG_RETURN(VARIANT_LIST, result); }
/*********************************************************************************************************************************** Process config protocol requests ***********************************************************************************************************************************/ bool configProtocol(const String *command, const VariantList *paramList, ProtocolServer *server) { FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_PARAM(STRING, command); FUNCTION_LOG_PARAM(VARIANT_LIST, paramList); FUNCTION_LOG_PARAM(PROTOCOL_SERVER, server); FUNCTION_LOG_END(); ASSERT(command != NULL); // Attempt to satisfy the request -- we may get requests that are meant for other handlers bool found = true; MEM_CONTEXT_TEMP_BEGIN() { if (strEq(command, PROTOCOL_COMMAND_CONFIG_OPTION_STR)) { VariantList *optionList = varLstNew(); for (unsigned int optionIdx = 0; optionIdx < varLstSize(paramList); optionIdx++) varLstAdd(optionList, varDup(cfgOption(cfgOptionId(strPtr(varStr(varLstGet(paramList, optionIdx))))))); protocolServerResponse(server, varNewVarLst(optionList)); } else found = false; } MEM_CONTEXT_TEMP_END(); FUNCTION_LOG_RETURN(BOOL, found); }
/*********************************************************************************************************************************** Determine which WAL files need to be pushed to the archive when in async mode This is the heart of the "look ahead" functionality in async archiving. Any files in the out directory that do not end in ok are removed and any ok files that do not have a corresponding ready file in archive_status (meaning it has been acknowledged by PostgreSQL) are removed. Then all ready files that do not have a corresponding ok file (meaning it has already been processed) are returned for processing. ***********************************************************************************************************************************/ static StringList * archivePushProcessList(const String *walPath) { FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_PARAM(STRING, walPath); FUNCTION_LOG_END(); ASSERT(walPath != NULL); StringList *result = NULL; MEM_CONTEXT_TEMP_BEGIN() { // Create the spool out path if it does not already exist storagePathCreateNP(storageSpoolWrite(), STORAGE_SPOOL_ARCHIVE_OUT_STR); // Read the status files from the spool directory, then remove any files that do not end in ok and create a list of the // ok files for further processing StringList *statusList = strLstSort( storageListP(storageSpool(), STORAGE_SPOOL_ARCHIVE_OUT_STR, .errorOnMissing = true), sortOrderAsc); StringList *okList = strLstNew(); for (unsigned int statusIdx = 0; statusIdx < strLstSize(statusList); statusIdx++) { const String *statusFile = strLstGet(statusList, statusIdx); if (strEndsWithZ(statusFile, STATUS_EXT_OK)) strLstAdd(okList, strSubN(statusFile, 0, strSize(statusFile) - STATUS_EXT_OK_SIZE)); else { storageRemoveP( storageSpoolWrite(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s", strPtr(statusFile)), .errorOnMissing = true); } } // Read the ready files from the archive_status directory StringList *readyList = archivePushReadyList(walPath); // Remove ok files that are not in the ready list StringList *okRemoveList = strLstMergeAnti(okList, readyList); for (unsigned int okRemoveIdx = 0; okRemoveIdx < strLstSize(okRemoveList); okRemoveIdx++) { storageRemoveP( storageSpoolWrite(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s" STATUS_EXT_OK, strPtr(strLstGet(okRemoveList, okRemoveIdx))), .errorOnMissing = true); } // Return all ready files that are not in the ok list result = strLstMove(strLstMergeAnti(readyList, okList), MEM_CONTEXT_OLD()); } MEM_CONTEXT_TEMP_END(); FUNCTION_LOG_RETURN(STRING_LIST, result); }
/*********************************************************************************************************************************** Catch signals ***********************************************************************************************************************************/ static void exitOnSignal(int signalType) { FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_PARAM(INT, signalType); FUNCTION_LOG_END(); exit(exitSafe(errorTypeCode(&TermError), false, (SignalType)signalType)); FUNCTION_LOG_RETURN_VOID(); }
/*********************************************************************************************************************************** End the command ***********************************************************************************************************************************/ void cmdEnd(int code, const String *errorMessage) { FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_PARAM(INT, code); FUNCTION_LOG_PARAM(STRING, errorMessage); FUNCTION_LOG_END(); ASSERT(cfgCommand() != cfgCmdNone); // Skip this log message if it won't be output. It's not too expensive but since we skipped cmdBegin(), may as well. if (logWill(cfgLogLevelDefault())) { MEM_CONTEXT_TEMP_BEGIN() { // Basic info on command end String *info = strNewFmt("%s command end: ", cfgCommandName(cfgCommand())); if (errorMessage == NULL) { strCat(info, "completed successfully"); if (cfgOptionValid(cfgOptLogTimestamp) && cfgOptionBool(cfgOptLogTimestamp)) strCatFmt(info, " (%" PRIu64 "ms)", timeMSec() - timeBegin); } else strCat(info, strPtr(errorMessage)); LOG(cfgLogLevelDefault(), 0, strPtr(info)); } MEM_CONTEXT_TEMP_END(); } // Reset timeBegin in case there is another command following this one timeBegin = timeMSec(); FUNCTION_LOG_RETURN_VOID(); }
/*********************************************************************************************************************************** Clean the queue and prepare a list of WAL segments that the async process should get ***********************************************************************************************************************************/ static StringList * queueNeed(const String *walSegment, bool found, uint64_t queueSize, size_t walSegmentSize, unsigned int pgVersion) { FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_PARAM(STRING, walSegment); FUNCTION_LOG_PARAM(BOOL, found); FUNCTION_LOG_PARAM(UINT64, queueSize); FUNCTION_LOG_PARAM(SIZE, walSegmentSize); FUNCTION_LOG_PARAM(UINT, pgVersion); FUNCTION_LOG_END(); ASSERT(walSegment != NULL); StringList *result = strLstNew(); MEM_CONTEXT_TEMP_BEGIN() { // Determine the first WAL segment for the async process to get. If the WAL segment requested by // PostgreSQL was not found then use that. If the segment was found but the queue is not full then // start with the next segment. const String *walSegmentFirst = found ? walSegmentNext(walSegment, walSegmentSize, pgVersion) : walSegment; // Determine how many WAL segments should be in the queue. The queue total must be at least 2 or it doesn't make sense to // have async turned on at all. unsigned int walSegmentQueueTotal = (unsigned int)(queueSize / walSegmentSize); if (walSegmentQueueTotal < 2) walSegmentQueueTotal = 2; // Build the ideal queue -- the WAL segments we want in the queue after the async process has run StringList *idealQueue = walSegmentRange(walSegmentFirst, walSegmentSize, pgVersion, walSegmentQueueTotal); // Get the list of files actually in the queue StringList *actualQueue = strLstSort( storageListP(storageSpool(), STORAGE_SPOOL_ARCHIVE_IN_STR, .errorOnMissing = true), sortOrderAsc); // Only preserve files that match the ideal queue. error/ok files are deleted so the async process can try again. RegExp *regExpPreserve = regExpNew(strNewFmt("^(%s)$", strPtr(strLstJoin(idealQueue, "|")))); // Build a list of WAL segments that are being kept so we can later make a list of what is needed StringList *keepQueue = strLstNew(); for (unsigned int actualQueueIdx = 0; actualQueueIdx < strLstSize(actualQueue); actualQueueIdx++) { // Get file from actual queue const String *file = strLstGet(actualQueue, actualQueueIdx); // Does this match a file we want to preserve? if (regExpMatch(regExpPreserve, file)) strLstAdd(keepQueue, file); // Else delete it else storageRemoveNP(storageSpoolWrite(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(file))); } // Generate a list of the WAL that are needed by removing kept WAL from the ideal queue for (unsigned int idealQueueIdx = 0; idealQueueIdx < strLstSize(idealQueue); idealQueueIdx++) { if (!strLstExists(keepQueue, strLstGet(idealQueue, idealQueueIdx))) strLstAdd(result, strLstGet(idealQueue, idealQueueIdx)); } } MEM_CONTEXT_TEMP_END(); FUNCTION_LOG_RETURN(STRING_LIST, result); }
/*********************************************************************************************************************************** Begin the command ***********************************************************************************************************************************/ void cmdBegin(bool logOption) { FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_PARAM(BOOL, logOption); FUNCTION_LOG_END(); ASSERT(cfgCommand() != cfgCmdNone); // This is fairly expensive log message to generate so skip it if it won't be output if (logWill(cfgLogLevelDefault())) { MEM_CONTEXT_TEMP_BEGIN() { // Basic info on command start String *info = strNewFmt("%s command begin", cfgCommandName(cfgCommand())); if (logOption) { strCatFmt(info, " %s:", PROJECT_VERSION); // Add command parameters if they exist const StringList *commandParamList = cfgCommandParam(); if (strLstSize(commandParamList) != 0) { strCatFmt(info, " ["); for (unsigned int commandParamIdx = 0; commandParamIdx < strLstSize(commandParamList); commandParamIdx++) { const String *commandParam = strLstGet(commandParamList, commandParamIdx); if (commandParamIdx != 0) strCatFmt(info, ", "); if (strchr(strPtr(commandParam), ' ') != NULL) commandParam = strNewFmt("\"%s\"", strPtr(commandParam)); strCat(info, strPtr(commandParam)); } strCatFmt(info, "]"); } // Loop though options and add the ones that are interesting for (ConfigOption optionId = 0; optionId < CFG_OPTION_TOTAL; optionId++) { // Skip the option if it is not valid if (!cfgOptionValid(optionId)) continue; // If option was negated if (cfgOptionNegate(optionId)) strCatFmt(info, " --no-%s", cfgOptionName(optionId)); // If option was reset else if (cfgOptionReset(optionId)) strCatFmt(info, " --reset-%s", cfgOptionName(optionId)); // Else set and not default else if (cfgOptionSource(optionId) != cfgSourceDefault && cfgOptionTest(optionId)) { ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId); // Don't show redacted options if (cfgDefOptionSecure(optionDefId)) strCatFmt(info, " --%s=<redacted>", cfgOptionName(optionId)); // Output boolean option else if (cfgDefOptionType(optionDefId) == cfgDefOptTypeBoolean) strCatFmt(info, " --%s", cfgOptionName(optionId)); // Output other options else { StringList *valueList = NULL; // Generate the values of hash options if (cfgDefOptionType(optionDefId) == cfgDefOptTypeHash) { valueList = strLstNew(); const KeyValue *optionKv = cfgOptionKv(optionId); const VariantList *keyList = kvKeyList(optionKv); for (unsigned int keyIdx = 0; keyIdx < varLstSize(keyList); keyIdx++) { strLstAdd( valueList, strNewFmt( "%s=%s", strPtr(varStr(varLstGet(keyList, keyIdx))), strPtr(varStrForce(kvGet(optionKv, varLstGet(keyList, keyIdx)))))); } } // Generate values for list options else if (cfgDefOptionType(optionDefId) == cfgDefOptTypeList) { valueList = strLstNewVarLst(cfgOptionLst(optionId)); } // Else only one value else { valueList = strLstNew(); strLstAdd(valueList, varStrForce(cfgOption(optionId))); } // Output options and values for (unsigned int valueListIdx = 0; valueListIdx < strLstSize(valueList); valueListIdx++) { const String *value = strLstGet(valueList, valueListIdx); strCatFmt(info, " --%s", cfgOptionName(optionId)); if (strchr(strPtr(value), ' ') != NULL) value = strNewFmt("\"%s\"", strPtr(value)); strCatFmt(info, "=%s", strPtr(value)); } } } } } LOG(cfgLogLevelDefault(), 0, strPtr(info)); } MEM_CONTEXT_TEMP_END(); } FUNCTION_LOG_RETURN_VOID(); }
/*********************************************************************************************************************************** Do cleanup and return result code ***********************************************************************************************************************************/ int exitSafe(int result, bool error, SignalType signalType) { FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_PARAM(INT, result); FUNCTION_LOG_PARAM(BOOL, error); FUNCTION_LOG_PARAM(ENUM, signalType); FUNCTION_LOG_END(); // Report error if one was thrown if (error) { // Don't log the error if it has already been logged by Perl #ifdef HAVE_LIBPERL if (strcmp(errorMessage(), PERL_EMBED_ERROR) != 0) { #endif LogLevel logLevel = errorCode() == errorTypeCode(&AssertError) ? logLevelAssert : logLevelError; // Assert errors always output a stack trace if (logLevel == logLevelAssert) LOG(logLevel, errorCode(), "%s\nSTACK TRACE:\n%s", errorMessage(), errorStackTrace()); else { // Log just the error to non-debug levels LOG_INTERNAL(logLevel, LOG_LEVEL_MIN, logLevelDetail, 0, errorCode(), errorMessage()); // Log the stack trace debug levels if (logAny(logLevelDebug)) { LOG_INTERNAL( logLevel, logLevelDebug, LOG_LEVEL_MAX, 0, errorCode(), "%s\nSTACK TRACE:\n%s", errorMessage(), errorStackTrace()); } } #ifdef HAVE_LIBPERL } #endif result = errorCode(); } // Free protocol objects but ignore errors TRY_BEGIN() { protocolFree(); } TRY_END(); // Free Perl but ignore errors #ifdef HAVE_LIBPERL TRY_BEGIN() { perlFree(result); } TRY_END(); #endif // Log command end if a command is set if (cfgCommand() != cfgCmdNone) { String *errorMessage = NULL; // On error generate an error message if (result != 0) { // On process terminate if (result == errorTypeCode(&TermError)) { errorMessage = strNew("terminated on signal "); // Terminate from a child if (signalType == signalTypeNone) strCat(errorMessage, "from child process"); // Else terminated directly else strCatFmt(errorMessage, "[SIG%s]", exitSignalName(signalType)); } // Standard error exit message else if (error) errorMessage = strNewFmt("aborted with exception [%03d]", result); } cmdEnd(result, errorMessage); } // Release any locks but ignore errors TRY_BEGIN() { lockRelease(false); } TRY_END(); // Return result - caller should immediate pass this result to exit() FUNCTION_LOG_RETURN(INT, result); }