/*********************************************************************************************************************************** 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 an archive file from the repository (WAL segment, history file, etc.) ***********************************************************************************************************************************/ int cmdArchiveGet(void) { FUNCTION_LOG_VOID(logLevelDebug); // Set the result assuming the archive file will not be found int result = 1; MEM_CONTEXT_TEMP_BEGIN() { // Check the parameters const StringList *commandParam = cfgCommandParam(); if (strLstSize(commandParam) != 2) { if (strLstSize(commandParam) == 0) THROW(ParamRequiredError, "WAL segment to get required"); if (strLstSize(commandParam) == 1) THROW(ParamRequiredError, "path to copy WAL segment required"); THROW(ParamInvalidError, "extra parameters found"); } // Get the segment name String *walSegment = strBase(strLstGet(commandParam, 0)); // Destination is wherever we were told to move the WAL segment const String *walDestination = walPath(strLstGet(commandParam, 1), cfgOptionStr(cfgOptPgPath), STR(cfgCommandName(cfgCommand()))); // Async get can only be performed on WAL segments, history or other files must use synchronous mode if (cfgOptionBool(cfgOptArchiveAsync) && walIsSegment(walSegment)) { bool found = false; // Has the WAL segment been found yet? bool queueFull = false; // Is the queue half or more full? bool forked = false; // Has the async process been forked yet? bool confessOnError = false; // Should we confess errors? // Loop and wait for the WAL segment to be pushed Wait *wait = waitNew((TimeMSec)(cfgOptionDbl(cfgOptArchiveTimeout) * MSEC_PER_SEC)); do { // Check for errors or missing files. For archive-get ok indicates that the process succeeded but there is no WAL // file to download. if (archiveAsyncStatus(archiveModeGet, walSegment, confessOnError)) { storageRemoveP( storageSpoolWrite(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s" STATUS_EXT_OK, strPtr(walSegment)), .errorOnMissing = true); break; } // Check if the WAL segment is already in the queue found = storageExistsNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))); // If found then move the WAL segment to the destination directory if (found) { // Source is the WAL segment in the spool queue StorageFileRead *source = storageNewReadNP( storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))); // A move will be attempted but if the spool queue and the WAL path are on different file systems then a copy // will be performed instead. // // It looks scary that we are disabling syncs and atomicity (in case we need to copy intead of move) but this // is safe because if the system crashes Postgres will not try to reuse a restored WAL segment but will instead // request it again using the restore_command. In the case of a move this hardly matters since path syncs are // cheap but if a copy is required we could save a lot of writes. StorageFileWrite *destination = storageNewWriteP( storageLocalWrite(), walDestination, .noCreatePath = true, .noSyncFile = true, .noSyncPath = true, .noAtomic = true); // Move (or copy if required) the file storageMoveNP(storageSpoolWrite(), source, destination); // Return success result = 0; // Get a list of WAL segments left in the queue StringList *queue = storageListP( storageSpool(), STORAGE_SPOOL_ARCHIVE_IN_STR, .expression = WAL_SEGMENT_REGEXP_STR); if (strLstSize(queue) > 0) { // Get size of the WAL segment uint64_t walSegmentSize = storageInfoNP(storageLocal(), walDestination).size; // Use WAL segment size to estimate queue size and determine if the async process should be launched queueFull = strLstSize(queue) * walSegmentSize > cfgOptionUInt64(cfgOptArchiveGetQueueMax) / 2; } } // If the WAL segment has not already been found then start the async process to get it. There's no point in // forking the async process off more than once so track that as well. Use an archive lock to prevent forking if // the async process was launched by another process. if (!forked && (!found || !queueFull) && lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 0, false)) { // Get control info PgControl pgControl = pgControlFromFile(cfgOptionStr(cfgOptPgPath)); // Create the queue storagePathCreateNP(storageSpoolWrite(), STORAGE_SPOOL_ARCHIVE_IN_STR); // The async process should not output on the console at all KeyValue *optionReplace = kvNew(); kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_CONSOLE_STR), VARSTRDEF("off")); kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_STDERR_STR), VARSTRDEF("off")); // Generate command options StringList *commandExec = cfgExecParam(cfgCmdArchiveGetAsync, optionReplace); strLstInsert(commandExec, 0, cfgExe()); // Clean the current queue using the list of WAL that we ideally want in the queue. queueNeed() // will return the list of WAL needed to fill the queue and this will be passed to the async process. const StringList *queue = queueNeed( walSegment, found, cfgOptionUInt64(cfgOptArchiveGetQueueMax), pgControl.walSegmentSize, pgControl.version); for (unsigned int queueIdx = 0; queueIdx < strLstSize(queue); queueIdx++) strLstAdd(commandExec, strLstGet(queue, queueIdx)); // Release the lock so the child process can acquire it lockRelease(true); // Fork off the async process if (forkSafe() == 0) { // Disable logging and close log file logClose(); // Detach from parent process forkDetach(); // Execute the binary. This statement will not return if it is successful. THROW_ON_SYS_ERROR( execvp(strPtr(cfgExe()), (char ** const)strLstPtr(commandExec)) == -1, ExecuteError, "unable to execute '" CFGCMD_ARCHIVE_GET_ASYNC "'"); } // Mark the async process as forked so it doesn't get forked again. A single run of the async process should be // enough to do the job, running it again won't help anything. forked = true; } // Exit loop if WAL was found if (found) break; // Now that the async process has been launched, confess any errors that are found confessOnError = true; } while (waitMore(wait)); } // Else perform synchronous get else {