1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-03-03 14:52:21 +02:00

Refactor lock module.

Refactor the lock module to split command-specific logic from the basic file locking functionality. Command specific logic is now in command/lock.c. This will make it easier to implement new features such as repository locking and updating lock file contents on remotes.

This implementation is essentially a drop-in replacement but there are a few differences. First, the lock names no longer require a path (the path is added in the lock module). Second, the timeout functionality has been removed since it was not being used.
This commit is contained in:
David Steele 2024-06-18 10:43:54 +08:00 committed by GitHub
parent ad7377c75b
commit 270dce41b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 689 additions and 720 deletions

View File

@ -470,7 +470,7 @@ HRN_FORK_BEGIN()
{
HRN_FORK_CHILD_BEGIN()
{
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock");
TEST_RESULT_BOOL(cmdLockAcquireP(), true, "create backup/expire lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -478,7 +478,7 @@ HRN_FORK_BEGIN()
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
cmdLockReleaseP();
}
HRN_FORK_CHILD_END();

View File

@ -30,6 +30,7 @@ src_doc = [
'../../src/command/command.c',
'../../src/command/exit.c',
'../../src/command/help/help.c',
'../../src/command/lock.c',
'../../src/common/compress/bz2/common.c',
'../../src/common/compress/bz2/compress.c',
'../../src/common/compress/bz2/decompress.c',

View File

@ -526,7 +526,7 @@ HRN_FORK_BEGIN()
{
HRN_FORK_CHILD_BEGIN()
{
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock");
TEST_RESULT_BOOL(cmdLockAcquireP(), true, "create backup/expire lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -534,7 +534,7 @@ HRN_FORK_BEGIN()
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
cmdLockReleaseP();
}
HRN_FORK_CHILD_END();

View File

@ -27,5 +27,18 @@
<p>Allow alternative WAL segment sizes for PostgreSQL &amp;le; 10.</p>
</release-item>
</release-improvement-list>
<release-development-list>
<release-item>
<github-pull-request id="2371"/>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="reid.thompson"/>
</release-item-contributor-list>
<p>Refactor lock module.</p>
</release-item>
</release-development-list>
</release-core-list>
</release>

View File

@ -78,6 +78,7 @@ SRCS = \
command/control/start.c \
command/control/stop.c \
command/local/local.c \
command/lock.c \
command/manifest/manifest.c \
command/repo/common.c \
command/repo/create.c \

View File

@ -13,6 +13,7 @@ Archive Get Command
#include "command/archive/get/file.h"
#include "command/archive/get/protocol.h"
#include "command/command.h"
#include "command/lock.h"
#include "common/debug.h"
#include "common/log.h"
#include "common/memContext.h"
@ -730,7 +731,7 @@ cmdArchiveGet(void)
// 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) && lockAcquireP(.returnOnNoLock = true))
if (!forked && (!found || !queueFull) && cmdLockAcquireP(.returnOnNoLock = true))
{
// Get control info
const PgControl pgControl = pgControlFromFile(storagePg(), cfgOptionStrNull(cfgOptPgVersionForce));
@ -761,7 +762,7 @@ cmdArchiveGet(void)
archiveAsyncErrorClear(archiveModeGet, walSegment);
// Release the lock so the child process can acquire it
lockRelease(true);
cmdLockReleaseP();
// Execute the async process
archiveAsyncExec(archiveModeGet, commandExec);

View File

@ -11,6 +11,7 @@ Archive Push Command
#include "command/archive/push/protocol.h"
#include "command/command.h"
#include "command/control/common.h"
#include "command/lock.h"
#include "common/compress/helper.h"
#include "common/debug.h"
#include "common/log.h"
@ -350,7 +351,7 @@ cmdArchivePush(void)
// If the WAL segment has not already been pushed then start the async process to push 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 more than one
// async process being launched.
if (!pushed && !forked && lockAcquireP(.returnOnNoLock = true))
if (!pushed && !forked && cmdLockAcquireP(.returnOnNoLock = true))
{
// The async process should not output on the console at all
KeyValue *const optionReplace = kvNew();
@ -367,7 +368,7 @@ cmdArchivePush(void)
archiveAsyncErrorClear(archiveModePush, archiveFile);
// Release the lock so the child process can acquire it
lockRelease(true);
cmdLockReleaseP();
// Execute the async process
archiveAsyncExec(archiveModePush, commandExec);

View File

@ -15,12 +15,12 @@ Backup Command
#include "command/backup/protocol.h"
#include "command/check/common.h"
#include "command/control/common.h"
#include "command/lock.h"
#include "command/stanza/common.h"
#include "common/compress/helper.h"
#include "common/crypto/cipherBlock.h"
#include "common/debug.h"
#include "common/io/filter/size.h"
#include "common/lock.h"
#include "common/log.h"
#include "common/regExp.h"
#include "common/time.h"
@ -1604,8 +1604,8 @@ backupJobResult(
if (percentComplete - *currentPercentComplete > 10)
{
*currentPercentComplete = percentComplete;
lockWriteDataP(
lockTypeBackup, .percentComplete = VARUINT(*currentPercentComplete), .sizeComplete = VARUINT64(*sizeProgress),
cmdLockWriteP(
.percentComplete = VARUINT(*currentPercentComplete), .sizeComplete = VARUINT64(*sizeProgress),
.size = VARUINT64(sizeTotal));
}
}
@ -2218,8 +2218,8 @@ backupProcess(const BackupData *const backupData, Manifest *const manifest, cons
// Initialize percent complete and bytes completed/total
unsigned int currentPercentComplete = 0;
lockWriteDataP(
lockTypeBackup, .percentComplete = VARUINT(currentPercentComplete), .sizeComplete = VARUINT64(sizeProgress),
cmdLockWriteP(
.percentComplete = VARUINT(currentPercentComplete), .sizeComplete = VARUINT64(sizeProgress),
.size = VARUINT64(sizeTotal));
MEM_CONTEXT_TEMP_RESET_BEGIN()

View File

@ -53,20 +53,19 @@ cmdStop(void)
// Find each lock file and send term signals to the processes
for (unsigned int lockPathFileIdx = 0; lockPathFileIdx < strLstSize(lockPathFileList); lockPathFileIdx++)
{
const String *lockFile = strLstGet(lockPathFileList, lockPathFileIdx);
const String *const lockFile = strLstGet(lockPathFileList, lockPathFileIdx);
// Skip any file that is not a lock file. Skip lock files for other stanzas if a stanza is provided.
if (!strEndsWithZ(lockFile, LOCK_FILE_EXT) || (stanzaPrefix != NULL && !strBeginsWith(lockFile, stanzaPrefix)))
continue;
// Read the lock file
lockFile = strNewFmt("%s/%s", strZ(lockPath), strZ(lockFile));
const LockReadResult lockResult = lockReadFileP(lockFile, .remove = true);
const LockReadResult lockResult = lockReadP(lockFile, .remove = true);
// If we cannot read the lock file for any reason then warn and continue to next file
if (lockResult.status != lockReadStatusValid)
{
LOG_WARN_FMT("unable to read lock file %s", strZ(lockFile));
LOG_WARN_FMT("unable to read lock file %s/%s", strZ(lockPath), strZ(lockFile));
continue;
}

View File

@ -8,8 +8,8 @@ Exit Routines
#include "command/command.h"
#include "command/exit.h"
#include "command/lock.h"
#include "common/debug.h"
#include "common/lock.h"
#include "common/log.h"
#include "config/config.h"
#include "protocol/helper.h"
@ -169,7 +169,7 @@ exitSafe(int result, const bool error, const SignalType signalType)
// Release any locks but ignore errors
TRY_BEGIN()
{
lockRelease(false);
cmdLockReleaseP(.returnOnNoLock = true);
}
TRY_END();

View File

@ -10,10 +10,10 @@ Info Command
#include "command/archive/common.h"
#include "command/info/info.h"
#include "command/lock.h"
#include "common/crypto/common.h"
#include "common/debug.h"
#include "common/io/fdWrite.h"
#include "common/lock.h"
#include "common/log.h"
#include "common/memContext.h"
#include "common/type/json.h"
@ -1299,7 +1299,7 @@ infoUpdateStanza(
if (!stanzaRepo->backupLockChecked)
{
// If there is a valid backup lock for this stanza then backup/expire must be running
const LockReadResult lockResult = lockRead(cfgOptionStr(cfgOptLockPath), stanzaRepo->name, lockTypeBackup);
const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name);
stanzaRepo->backupLockHeld = lockResult.status == lockReadStatusValid;
stanzaRepo->backupLockChecked = true;

149
src/command/lock.c Normal file
View File

@ -0,0 +1,149 @@
/***********************************************************************************************************************************
Command Lock Handler
***********************************************************************************************************************************/
#include "build.auto.h"
#include "command/lock.h"
#include "common/debug.h"
#include "common/log.h"
#include "config/config.h"
/***********************************************************************************************************************************
Local variables
***********************************************************************************************************************************/
static struct CmdLockLocal
{
bool held; // Is the lock being held?
} cmdLockLocal;
/***********************************************************************************************************************************
Lock type names
***********************************************************************************************************************************/
static const char *const lockTypeName[] =
{
"archive", // lockTypeArchive
"backup", // lockTypeBackup
};
/**********************************************************************************************************************************/
static String *
cmdLockFileName(const String *const stanza, const LockType lockType)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(ENUM, lockType);
FUNCTION_TEST_END();
ASSERT(stanza != NULL);
ASSERT(lockType < lockTypeAll);
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType]));
}
/**********************************************************************************************************************************/
FN_EXTERN bool
cmdLockAcquire(const LockAcquireParam param)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END();
bool result = true;
// Don't allow return on no lock when locking more than one file. This makes cleanup difficult and there are no known use cases.
const LockType lockType = cfgLockType();
ASSERT(!param.returnOnNoLock || lockType != lockTypeAll);
// Don't allow another lock if one is already held
if (cmdLockLocal.held)
THROW(AssertError, "lock is already held by this process");
// Lock files
const LockType lockMin = lockType == lockTypeAll ? lockTypeArchive : lockType;
const LockType lockMax = lockType == lockTypeAll ? (lockTypeAll - 1) : lockType;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
{
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockIdx);
result = lockAcquire(lockFileName, param);
strFree(lockFileName);
}
// Set lock held flag
cmdLockLocal.held = result;
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/
FN_EXTERN void
cmdLockWrite(const LockWriteParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(VARIANT, param.percentComplete);
FUNCTION_LOG_PARAM(VARIANT, param.sizeComplete);
FUNCTION_LOG_PARAM(VARIANT, param.size);
FUNCTION_LOG_END();
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), cfgLockType());
lockWrite(lockFileName, param);
strFree(lockFileName);
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
FN_EXTERN LockReadResult
cmdLockRead(const LockType lockType, const String *const stanza)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(ENUM, lockType);
FUNCTION_LOG_PARAM(STRING, stanza);
FUNCTION_LOG_END();
ASSERT(lockType == lockTypeBackup);
ASSERT(stanza != NULL);
FUNCTION_AUDIT_STRUCT();
LockReadResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
const String *const lockFileName = cmdLockFileName(stanza, lockType);
MEM_CONTEXT_PRIOR_BEGIN()
{
result = lockReadP(lockFileName);
}
MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_STRUCT(result);
}
/**********************************************************************************************************************************/
FN_EXTERN bool
cmdLockRelease(const LockReleaseParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END();
bool result = true;
// Release lock(s)
if (cmdLockLocal.held)
{
result = lockRelease(param);
cmdLockLocal.held = false;
}
// Else error when requested
else if (!param.returnOnNoLock)
THROW(AssertError, "no lock is held by this process");
FUNCTION_LOG_RETURN(BOOL, result);
}

35
src/command/lock.h Normal file
View File

@ -0,0 +1,35 @@
/***********************************************************************************************************************************
Command Lock Handler
***********************************************************************************************************************************/
#ifndef COMMAND_LOCK_H
#define COMMAND_LOCK_H
#include "common/lock.h"
#include "config/config.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Acquire a command lock. This will involve locking one or more files on disk depending on the lock type. Most operations only
// acquire a single lock type (archive or backup), but the stanza commands all need to lock both.
#define cmdLockAcquireP(...) \
cmdLockAcquire((LockAcquireParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool cmdLockAcquire(LockAcquireParam param);
// Write data to command lock file
#define cmdLockWriteP(...) \
cmdLockWrite((LockWriteParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN void cmdLockWrite(LockWriteParam param);
// Read a command lock file held by another process to get information about what the process is doing
FN_EXTERN LockReadResult cmdLockRead(LockType lockType, const String *stanza);
// Release command lock(s)
#define cmdLockReleaseP(...) \
cmdLockRelease((LockReleaseParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool cmdLockRelease(LockReleaseParam param);
#endif

View File

@ -8,6 +8,7 @@ Remote Command
#include "command/backup/blockIncr.h"
#include "command/backup/pageChecksum.h"
#include "command/control/common.h"
#include "command/lock.h"
#include "command/restore/blockChecksum.h"
#include "common/crypto/cipherBlock.h"
#include "common/crypto/hash.h"
@ -77,7 +78,7 @@ cmdRemote(ProtocolServer *const server)
lockStopTest();
// Acquire the lock
lockAcquireP();
cmdLockAcquireP();
}
}

View File

@ -38,42 +38,32 @@ Constants
#define LOCK_KEY_SIZE_COMPLETE STRID6("szCplt", 0x50c4286931)
#define LOCK_KEY_SIZE STRID5("sz", 0x3530)
/***********************************************************************************************************************************
Lock type names
***********************************************************************************************************************************/
static const char *const lockTypeName[] =
{
"archive", // lockTypeArchive
"backup", // lockTypeBackup
};
/***********************************************************************************************************************************
Mem context and local variables
***********************************************************************************************************************************/
typedef struct LockFile
{
String *name; // Name of lock file
int fd; // File descriptor for lock file
bool written; // Has the lock file been written?
} LockFile;
static struct LockLocal
{
MemContext *memContext; // Mem context for locks
const String *path; // Lock path
const String *execId; // Process exec id
const String *stanza; // Stanza
LockType type; // Lock type
bool held; // Is the lock being held?
struct
{
String *name; // Name of lock file
int fd; // File descriptor for lock file
} file[lockTypeAll];
List *lockList; // List of locks held
const Storage *storage; // Storage object for lock path
} lockLocal;
/**********************************************************************************************************************************/
FN_EXTERN void
lockInit(const String *const path, const String *const execId, const String *const stanza, const LockType type)
lockInit(const String *const path, const String *const execId)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, path);
FUNCTION_LOG_PARAM(STRING, execId);
FUNCTION_LOG_PARAM(ENUM, type);
FUNCTION_LOG_END();
ASSERT(lockLocal.memContext == NULL);
@ -86,8 +76,8 @@ lockInit(const String *const path, const String *const execId, const String *con
lockLocal.memContext = MEM_CONTEXT_NEW();
lockLocal.path = strDup(path);
lockLocal.execId = strDup(execId);
lockLocal.stanza = strDup(stanza);
lockLocal.type = type;
lockLocal.lockList = lstNewP(sizeof(LockFile), .comparator = lstComparatorStr);
lockLocal.storage = storagePosixNewP(lockLocal.path, .write = true);
}
MEM_CONTEXT_NEW_END();
}
@ -96,38 +86,24 @@ lockInit(const String *const path, const String *const execId, const String *con
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
FN_EXTERN String *
lockFileName(const String *const stanza, const LockType lockType)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(ENUM, lockType);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType]));
}
/***********************************************************************************************************************************
Read contents of lock file
If a seek is required to get to the beginning of the data, that must be done before calling this function.
***********************************************************************************************************************************/
// Size of initial buffer used to load lock file
#define LOCK_BUFFER_SIZE 128
// Helper to read data
static LockData
lockReadFileData(const String *const lockFile, const int fd)
lockReadData(const String *const lockFileName, const int fd)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, lockFile);
FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(INT, fd);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(lockFile != NULL);
ASSERT(lockFileName != NULL);
ASSERT(fd != -1);
LockData result = {0};
@ -140,7 +116,7 @@ lockReadFileData(const String *const lockFile, const int fd)
Buffer *const buffer = bufNew(LOCK_BUFFER_SIZE);
IoWrite *const write = ioBufferWriteNewOpen(buffer);
ioCopyP(ioFdReadNewOpen(lockFile, fd, 0), write);
ioCopyP(ioFdReadNewOpen(lockFileName, fd, 0), write);
ioWriteClose(write);
JsonRead *const json = jsonReadNew(strNewBuf(buffer));
@ -167,34 +143,165 @@ lockReadFileData(const String *const lockFile, const int fd)
}
CATCH_ANY()
{
THROWP_FMT(errorType(), "unable to read lock file '%s': %s", strZ(lockFile), errorMessage());
THROWP_FMT(errorType(), "unable to read lock file '%s': %s", strZ(lockFileName), errorMessage());
}
TRY_END();
FUNCTION_LOG_RETURN_STRUCT(result);
}
/**********************************************************************************************************************************/
FN_EXTERN bool
lockAcquire(const String *const lockFileName, const LockAcquireParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END();
ASSERT(lockFileName != NULL);
ASSERT(lockLocal.memContext != NULL);
bool result = false;
if (lstFind(lockLocal.lockList, &lockFileName) != NULL)
THROW_FMT(AssertError, "lock on file '%s' already held", strZ(lockFileName));
MEM_CONTEXT_TEMP_BEGIN()
{
const String *const lockFilePath = storagePathP(lockLocal.storage, lockFileName);
bool retry;
int errNo = 0;
int fd = -1;
do
{
// Assume there will be no retry
retry = false;
// Attempt to open the file
if ((fd = open(strZ(lockFilePath), O_RDWR | O_CREAT, STORAGE_MODE_FILE_DEFAULT)) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// If the path does not exist then create it
if (errNo == ENOENT)
{
storagePathCreateP(storagePosixNewP(FSLASH_STR, .write = true), strPath(lockFilePath));
retry = true;
}
}
else
{
// Attempt to lock the file
if (flock(fd, LOCK_EX | LOCK_NB) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// Get execId from lock file and close it
const String *execId = NULL;
TRY_BEGIN()
{
execId = lockReadData(lockFileName, fd).execId;
}
CATCH_ANY()
{
// Any errors will be reported as unable to acquire a lock. If a process is trying to get a lock but is not
// synchronized with the process holding the actual lock, it is possible that it could see a short read or
// have some other problem reading. Reporting the error will likely be misleading when the actual problem is
// that another process owns the lock file.
}
FINALLY()
{
close(fd);
}
TRY_END();
// Even though we were unable to lock the file, it may be that it is already locked by another process with the
// same exec-id, i.e. spawned by the same original main process. If so, report the lock as successful.
if (strEq(execId, lockLocal.execId))
fd = LOCK_ON_EXEC_ID;
else
fd = -1;
}
}
}
while (fd == -1 && retry);
// If the lock was not successful
if (fd == -1)
{
// Error when requested
if (!param.returnOnNoLock || errNo != EWOULDBLOCK)
{
const String *errorHint = NULL;
if (errNo == EWOULDBLOCK)
errorHint = strNewZ("\nHINT: is another " PROJECT_NAME " process running?");
else if (errNo == EACCES)
{
// Get information for the current user
userInit();
errorHint = strNewFmt(
"\nHINT: does '%s:%s' running " PROJECT_NAME " have permissions on the '%s' file?", strZ(userName()),
strZ(groupName()), strZ(lockFilePath));
}
THROW_FMT(
LockAcquireError, "unable to acquire lock on file '%s': %s%s", strZ(lockFilePath), strerror(errNo),
errorHint == NULL ? "" : strZ(errorHint));
}
}
// Else add lock to list
else
{
MEM_CONTEXT_OBJ_BEGIN(lockLocal.lockList)
{
lstAdd(lockLocal.lockList, &(LockFile){.name = strDup(lockFileName), .fd = fd});
}
MEM_CONTEXT_OBJ_END();
// Write lock data unless lock was acquired by matching execId
if (fd != LOCK_ON_EXEC_ID)
lockWriteP(lockFileName);
// Success
result = true;
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/
FN_EXTERN LockReadResult
lockReadFile(const String *const lockFile, const LockReadFileParam param)
lockRead(const String *const lockFileName, const LockReadParam param)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, lockFile);
FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(BOOL, param.remove);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(lockFile != NULL);
ASSERT(lockLocal.memContext != NULL);
ASSERT(lockFileName != NULL);
LockReadResult result = {.status = lockReadStatusValid};
MEM_CONTEXT_TEMP_BEGIN()
{
// If we cannot open the lock file for any reason then warn and continue to next file
const String *const lockFilePath = storagePathP(lockLocal.storage, lockFileName);
// If we cannot open the lock file for any reason then warn
int fd = -1;
if ((fd = open(strZ(lockFile), O_RDONLY, 0)) == -1)
if ((fd = open(strZ(lockFilePath), O_RDONLY, 0)) == -1)
{
result.status = lockReadStatusMissing;
}
@ -212,7 +319,7 @@ lockReadFile(const String *const lockFile, const LockReadFileParam param)
{
MEM_CONTEXT_PRIOR_BEGIN()
{
result.data = lockReadFileData(lockFile, fd);
result.data = lockReadData(lockFilePath, fd);
}
MEM_CONTEXT_PRIOR_END();
}
@ -225,7 +332,7 @@ lockReadFile(const String *const lockFile, const LockReadFileParam param)
// Remove lock file if requested but do not report failures
if (param.remove)
unlink(strZ(lockFile));
unlink(strZ(lockFilePath));
// Close after unlinking to prevent a race condition where another process creates the file as we remove it
close(fd);
@ -237,50 +344,17 @@ lockReadFile(const String *const lockFile, const LockReadFileParam param)
}
/**********************************************************************************************************************************/
FN_EXTERN LockReadResult
lockRead(const String *const lockPath, const String *const stanza, const LockType lockType)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, lockPath);
FUNCTION_LOG_PARAM(STRING, stanza);
FUNCTION_LOG_PARAM(ENUM, lockType);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
LockReadResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
const String *const lockFile = strNewFmt("%s/%s", strZ(lockPath), strZ(lockFileName(stanza, lockType)));
MEM_CONTEXT_PRIOR_BEGIN()
{
result = lockReadFileP(lockFile);
}
MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_STRUCT(result);
}
/***********************************************************************************************************************************
Write contents of lock file
***********************************************************************************************************************************/
FN_EXTERN void
lockWriteData(const LockType lockType, const LockWriteDataParam param)
lockWrite(const String *const lockFileName, const LockWriteParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(ENUM, lockType);
FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(VARIANT, param.percentComplete);
FUNCTION_LOG_PARAM(VARIANT, param.sizeComplete);
FUNCTION_LOG_PARAM(VARIANT, param.size);
FUNCTION_LOG_END();
ASSERT(lockType < lockTypeAll);
ASSERT(lockLocal.file[lockType].name != NULL);
ASSERT(lockLocal.file[lockType].fd != -1);
ASSERT(lockFileName != NULL);
MEM_CONTEXT_TEMP_BEGIN()
{
@ -303,24 +377,31 @@ lockWriteData(const LockType lockType, const LockWriteDataParam param)
jsonWriteObjectEnd(json);
if (lockType == lockTypeBackup && lockLocal.held)
{
// Seek to beginning of backup lock file
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockType].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockType].name));
// Write to lock file
LockFile *const lockFile = lstFind(lockLocal.lockList, &lockFileName);
// In case the current write is ever shorter than the previous one
if (lockFile == NULL)
THROW_FMT(AssertError, "lock file '%s' not found", strZ(lockFileName));
const String *const lockFilePath = storagePathP(lockLocal.storage, lockFileName);
if (lockFile->written)
{
// Seek to beginning of lock file
THROW_ON_SYS_ERROR_FMT(
ftruncate(lockLocal.file[lockType].fd, 0) == -1, FileWriteError, "unable to truncate '%s'",
strZ(lockLocal.file[lockType].name));
lseek(lockFile->fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0, strZ(lockFilePath));
// In case the current write is shorter than before
THROW_ON_SYS_ERROR_FMT(ftruncate(lockFile->fd, 0) == -1, FileWriteError, "unable to truncate '%s'", strZ(lockFilePath));
}
// Write lock file data
IoWrite *const write = ioFdWriteNewOpen(lockLocal.file[lockType].name, lockLocal.file[lockType].fd, 0);
IoWrite *const write = ioFdWriteNewOpen(lockFilePath, lockFile->fd, 0);
ioCopyP(ioBufferReadNewOpen(BUFSTR(jsonWriteResult(json))), write);
ioWriteClose(write);
lockFile->written = true;
}
MEM_CONTEXT_TEMP_END();
@ -328,219 +409,44 @@ lockWriteData(const LockType lockType, const LockWriteDataParam param)
}
/**********************************************************************************************************************************/
// Helper to acquire a file lock
static int
lockAcquireFile(const String *const lockFile, const TimeMSec lockTimeout, const bool failOnNoLock)
FN_EXTERN bool
lockRelease(const LockReleaseParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, lockFile);
FUNCTION_LOG_PARAM(TIMEMSEC, lockTimeout);
FUNCTION_LOG_PARAM(BOOL, failOnNoLock);
FUNCTION_LOG_END();
ASSERT(lockFile != NULL);
int result = -1;
MEM_CONTEXT_TEMP_BEGIN()
{
Wait *const wait = waitNew(lockTimeout);
bool retry;
int errNo = 0;
do
{
// Assume there will be no retry
retry = false;
// Attempt to open the file
if ((result = open(strZ(lockFile), O_RDWR | O_CREAT, STORAGE_MODE_FILE_DEFAULT)) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// If the path does not exist then create it
if (errNo == ENOENT)
{
storagePathCreateP(storagePosixNewP(FSLASH_STR, .write = true), strPath(lockFile));
retry = true;
}
}
else
{
// Attempt to lock the file
if (flock(result, LOCK_EX | LOCK_NB) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// Get execId from lock file and close it
const String *execId = NULL;
TRY_BEGIN()
{
execId = lockReadFileData(lockFile, result).execId;
}
CATCH_ANY()
{
// Any errors will be reported as unable to acquire a lock. If a process is trying to get a lock but is not
// synchronized with the process holding the actual lock, it is possible that it could see a short read or
// have some other problem reading. Reporting the error will likely be misleading when the actual problem is
// that another process owns the lock file.
}
FINALLY()
{
close(result);
}
TRY_END();
// Even though we were unable to lock the file, it may be that it is already locked by another process with the
// same exec-id, i.e. spawned by the same original main process. If so, report the lock as successful.
if (strEq(execId, lockLocal.execId))
result = LOCK_ON_EXEC_ID;
else
result = -1;
}
}
}
while (result == -1 && (waitMore(wait) || retry));
// If the lock was not successful
if (result == -1)
{
// Error when requested
if (failOnNoLock || errNo != EWOULDBLOCK)
{
const String *errorHint = NULL;
if (errNo == EWOULDBLOCK)
errorHint = strNewZ("\nHINT: is another " PROJECT_NAME " process running?");
else if (errNo == EACCES)
{
// Get information for the current user
userInit();
errorHint = strNewFmt(
"\nHINT: does '%s:%s' running " PROJECT_NAME " have permissions on the '%s' file?", strZ(userName()),
strZ(groupName()), strZ(lockFile));
}
THROW_FMT(
LockAcquireError, "unable to acquire lock on file '%s': %s%s", strZ(lockFile), strerror(errNo),
errorHint == NULL ? "" : strZ(errorHint));
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(INT, result);
}
FN_EXTERN bool
lockAcquire(const LockAcquireParam param)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(TIMEMSEC, param.timeout);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END();
ASSERT(lockLocal.memContext != NULL);
bool result = true;
// Don't allow failures when locking more than one file. This makes cleanup difficult and there are no known use cases.
ASSERT(!param.returnOnNoLock || lockLocal.type != lockTypeAll);
// Don't allow another lock if one is already held
if (lockLocal.held)
THROW(AssertError, "lock is already held by this process");
// Lock files
const LockType lockMin = lockLocal.type == lockTypeAll ? lockTypeArchive : lockLocal.type;
const LockType lockMax = lockLocal.type == lockTypeAll ? (lockTypeAll - 1) : lockLocal.type;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
{
MEM_CONTEXT_BEGIN(lockLocal.memContext)
{
strFree(lockLocal.file[lockIdx].name);
lockLocal.file[lockIdx].name = strNewFmt("%s/%s", strZ(lockLocal.path), strZ(lockFileName(lockLocal.stanza, lockIdx)));
}
MEM_CONTEXT_END();
lockLocal.file[lockIdx].fd = lockAcquireFile(lockLocal.file[lockIdx].name, param.timeout, !param.returnOnNoLock);
if (lockLocal.file[lockIdx].fd == -1)
{
// Free the lock
lockLocal.held = false;
result = false;
break;
}
// Else write lock data unless we locked an execId match
else if (lockLocal.file[lockIdx].fd != LOCK_ON_EXEC_ID)
lockWriteDataP(lockIdx);
}
if (result)
lockLocal.held = true;
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/
// Helper to release a file lock
static void
lockReleaseFile(const int lockFd, const String *const lockFile)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(INT, lockFd);
FUNCTION_LOG_PARAM(STRING, lockFile);
FUNCTION_LOG_END();
// Can't release lock if there isn't one
ASSERT(lockFd >= 0);
MEM_CONTEXT_TEMP_BEGIN()
{
// Remove file first and then close it to release the lock. If we close it first then another process might grab the lock
// right before the delete which means the file locked by the other process will get deleted.
storageRemoveP(storagePosixNewP(FSLASH_STR, .write = true), lockFile);
close(lockFd);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
FN_EXTERN bool
lockRelease(bool failOnNoLock)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(BOOL, failOnNoLock);
FUNCTION_LOG_END();
bool result = false;
if (!lockLocal.held)
// Nothing to do if lock list is empty
if (lstEmpty(lockLocal.lockList))
{
if (failOnNoLock)
// Fail if requested
if (!param.returnOnNoLock)
THROW(AssertError, "no lock is held by this process");
}
// Else release all locks
else
{
// Release locks
const LockType lockMin = lockLocal.type == lockTypeAll ? lockTypeArchive : lockLocal.type;
const LockType lockMax = lockLocal.type == lockTypeAll ? (lockTypeAll - 1) : lockLocal.type;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
// Release until list is empty
while (!lstEmpty(lockLocal.lockList))
{
if (lockLocal.file[lockIdx].fd != LOCK_ON_EXEC_ID)
lockReleaseFile(lockLocal.file[lockIdx].fd, lockLocal.file[lockIdx].name);
LockFile *const lockFile = lstGet(lockLocal.lockList, 0);
// Remove lock file if this lock was not acquired by matching execId
if (lockFile->fd != LOCK_ON_EXEC_ID)
{
storageRemoveP(lockLocal.storage, lockFile->name);
close(lockFile->fd);
}
strFree(lockFile->name);
lstRemoveIdx(lockLocal.lockList, 0);
}
// Free the lock context
lockLocal.held = false;
// Success
result = true;
}

View File

@ -7,16 +7,8 @@ Lock Handler
#include <sys/types.h>
/***********************************************************************************************************************************
Lock types
Lock data
***********************************************************************************************************************************/
typedef enum
{
lockTypeArchive,
lockTypeBackup,
lockTypeAll,
lockTypeNone,
} LockType;
#include "common/type/variant.h"
// Lock data
@ -40,44 +32,35 @@ Constants
Functions
***********************************************************************************************************************************/
// Initialize lock module
FN_EXTERN void lockInit(const String *path, const String *execId, const String *stanza, LockType type);
FN_EXTERN void lockInit(const String *path, const String *execId);
// Acquire a lock type. This will involve locking one or more files on disk depending on the lock type. Most operations only take a
// single lock (archive or backup), but the stanza commands all need to lock both.
// Acquire a lock
typedef struct LockAcquireParam
{
VAR_PARAM_HEADER;
TimeMSec timeout; // Lock timeout
bool returnOnNoLock; // Return when no lock acquired (rather than throw an error)
} LockAcquireParam;
#define lockAcquireP(...) \
lockAcquire((LockAcquireParam) {VAR_PARAM_INIT, __VA_ARGS__})
#define lockAcquireP(lockFileName, ...) \
lockAcquire(lockFileName, (LockAcquireParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool lockAcquire(LockAcquireParam param);
// Release a lock
FN_EXTERN bool lockRelease(bool failOnNoLock);
// Build lock file name
FN_EXTERN String *lockFileName(const String *stanza, LockType lockType);
FN_EXTERN bool lockAcquire(const String *lockFileName, LockAcquireParam param);
// Write data to a lock file
typedef struct LockWriteDataParam
typedef struct LockWriteParam
{
VAR_PARAM_HEADER;
const Variant *percentComplete; // Percentage of backup complete * 100 (when not NULL)
const Variant *sizeComplete; // Completed size of the backup in bytes
const Variant *size; // Total size of the backup in bytes
} LockWriteDataParam;
} LockWriteParam;
#define lockWriteDataP(lockType, ...) \
lockWriteData(lockType, (LockWriteDataParam) {VAR_PARAM_INIT, __VA_ARGS__})
#define lockWriteP(lockFileName, ...) \
lockWrite(lockFileName, (LockWriteParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN void lockWriteData(LockType lockType, LockWriteDataParam param);
FN_EXTERN void lockWrite(const String *lockFileName, LockWriteParam param);
// Read a lock file held by another process to get information about what the process is doing. This is a lower-level version to use
// when the lock file name is already known and the lock file may need to be removed.
// Read a lock file held by another process to get information about what the process is doing
typedef enum
{
lockReadStatusMissing, // File is missing
@ -92,18 +75,27 @@ typedef struct LockReadResult
LockData data; // Lock data
} LockReadResult;
typedef struct LockReadFileParam
typedef struct LockReadParam
{
VAR_PARAM_HEADER;
bool remove; // Remove the lock file after locking/reading
} LockReadFileParam;
} LockReadParam;
#define lockReadFileP(lockFile, ...) \
lockReadFile(lockFile, (LockReadFileParam){VAR_PARAM_INIT, __VA_ARGS__})
#define lockReadP(lockFileName, ...) \
lockRead(lockFileName, (LockReadParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN LockReadResult lockReadFile(const String *lockFile, LockReadFileParam param);
FN_EXTERN LockReadResult lockRead(const String *lockFileName, LockReadParam param);
// Wrapper that generates the lock filename before calling lockReadFile()
FN_EXTERN LockReadResult lockRead(const String *lockPath, const String *stanza, LockType lockType);
// Release lock(s)
typedef struct LockReleaseParam
{
VAR_PARAM_HEADER;
bool returnOnNoLock; // Return when no lock is held (rather than throw an error)
} LockReleaseParam;
#define lockReleaseP(...) \
lockRelease((LockReleaseParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool lockRelease(LockReleaseParam param);
#endif

View File

@ -7,7 +7,6 @@ sets the command and options and determines which options are valid for a comman
#ifndef CONFIG_CONFIG_H
#define CONFIG_CONFIG_H
#include "common/lock.h"
#include "common/log.h"
#include "common/type/stringId.h"
#include "common/type/stringList.h"
@ -42,6 +41,17 @@ typedef enum
#define CFG_COMMAND_ROLE_TOTAL 4
/***********************************************************************************************************************************
Lock types
***********************************************************************************************************************************/
typedef enum
{
lockTypeArchive,
lockTypeBackup,
lockTypeAll,
lockTypeNone,
} LockType;
/***********************************************************************************************************************************
Command Functions

View File

@ -8,12 +8,12 @@ Configuration Load
#include <unistd.h>
#include "command/command.h"
#include "command/lock.h"
#include "common/compress/helper.intern.h"
#include "common/crypto/common.h"
#include "common/debug.h"
#include "common/io/io.h"
#include "common/io/socket/common.h"
#include "common/lock.h"
#include "common/log.h"
#include "common/memContext.h"
#include "config/config.intern.h"
@ -566,15 +566,13 @@ cfgLoad(const unsigned int argListSize, const char *argList[])
// Begin the command
cmdBegin();
// Init lock module if this command can lock
if (cfgLockType() != lockTypeNone && !cfgCommandHelp())
{
lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), cfgLockType());
// Initialize the lock module
if (cfgOptionTest(cfgOptLockPath))
lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId));
// Acquire a lock if this command requires a lock
if (cfgLockRequired())
lockAcquireP();
}
// Acquire a lock if this command requires a lock
if (cfgLockType() != lockTypeNone && !cfgCommandHelp() && cfgLockRequired())
cmdLockAcquireP();
// Update options that have complex rules
cfgLoadUpdateOption();

View File

@ -20,6 +20,7 @@ Main
#include "command/help/help.h"
#include "command/info/info.h"
#include "command/local/local.h"
#include "command/lock.h"
#include "command/manifest/manifest.h"
#include "command/remote/remote.h"
#include "command/repo/create.h"
@ -180,7 +181,7 @@ main(int argListSize, const char *argList[])
cmdBegin();
// Null out any backup percent complete value in the backup lock file
lockWriteDataP(lockTypeBackup);
cmdLockWriteP();
// Run expire
cmdExpire();

View File

@ -146,6 +146,7 @@ src_pgbackrest = [
'command/control/start.c',
'command/control/stop.c',
'command/local/local.c',
'command/lock.c',
'command/manifest/manifest.c',
'command/repo/common.c',
'command/repo/create.c',

View File

@ -425,7 +425,7 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: lock
total: 3
total: 1
harness:
name: config
shim:
@ -441,7 +441,7 @@ unit:
- common/lock
depend:
- common/lock
- command/lock
- config/common
- config/config
- config/parse
@ -789,6 +789,14 @@ unit:
- name: command
test:
# ----------------------------------------------------------------------------------------------------------------------------
- name: lock
total: 1
coverage:
- command/lock
# ----------------------------------------------------------------------------------------------------------------------------
- name: control
total: 4
@ -798,6 +806,9 @@ unit:
- command/control/start
- command/control/stop
include:
- command/lock
# ----------------------------------------------------------------------------------------------------------------------------
- name: annotate
total: 1
@ -892,6 +903,9 @@ unit:
coverage:
- command/info/info
include:
- command/lock
# ----------------------------------------------------------------------------------------------------------------------------
- name: backup
total: 12

View File

@ -9,7 +9,6 @@ Harness for Creating Test Backups
#include "common/compress/helper.h"
#include "common/crypto/common.h"
#include "common/crypto/hash.h"
#include "common/lock.h"
#include "config/config.h"
#include "info/infoArchive.h"
#include "info/manifest.h"
@ -153,8 +152,8 @@ hrnCmdBackup(void)
{
FUNCTION_HARNESS_VOID();
lockInit(STR(testPath()), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), lockTypeBackup);
lockAcquireP();
lockInit(STR(testPath()), cfgOptionStr(cfgOptExecId));
cmdLockAcquireP();
TRY_BEGIN()
{
@ -162,7 +161,7 @@ hrnCmdBackup(void)
}
FINALLY()
{
lockRelease(true);
cmdLockReleaseP();
}
TRY_END();

View File

@ -108,8 +108,8 @@ hrnCfgLoad(ConfigCommand commandId, const StringList *argListParam, const HrnCfg
if (cfgOptionValid(cfgOptExecId) && !cfgOptionTest(cfgOptExecId))
cfgOptionSet(cfgOptExecId, cfgSourceParam, VARSTRDEF("1-test"));
if (cfgOptionTest(cfgOptExecId) && cfgOptionTest(cfgOptLockPath) && cfgOptionTest(cfgOptStanza))
lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), cfgLockType());
if (cfgOptionTest(cfgOptExecId) && cfgOptionTest(cfgOptLockPath))
lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId));
else
hrnLockUnInit();

View File

@ -13,17 +13,15 @@ Include shimmed C modules
/**********************************************************************************************************************************/
FN_EXTERN void
lockInit(const String *const path, const String *const execId, const String *const stanza, const LockType type)
lockInit(const String *const path, const String *const execId)
{
FUNCTION_HARNESS_BEGIN();
FUNCTION_HARNESS_PARAM(STRING, path);
FUNCTION_HARNESS_PARAM(STRING, execId);
FUNCTION_HARNESS_PARAM(STRING, stanza);
FUNCTION_HARNESS_PARAM(ENUM, type);
FUNCTION_HARNESS_END();
hrnLockUnInit();
lockInit_SHIMMED(path, execId, stanza, type);
lockInit_SHIMMED(path, execId);
FUNCTION_HARNESS_RETURN_VOID();
}

View File

@ -27,6 +27,7 @@ src_test = [
'../../src/command/command.c',
'../../src/command/exit.c',
'../../src/command/help/help.c',
'../../src/command/lock.c',
'../../src/common/compress/bz2/common.c',
'../../src/common/compress/bz2/decompress.c',
'../../src/common/fork.c',

View File

@ -742,8 +742,8 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-dededede"), cfgOptionStr(cfgOptStanza), cfgLockType());
TEST_RESULT_VOID(lockAcquireP(.timeout = 30000, .returnOnNoLock = true), "acquire lock");
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-dededede"));
TEST_RESULT_VOID(cmdLockAcquireP(.returnOnNoLock = true), "acquire lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -751,7 +751,7 @@ testRun(void)
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
cmdLockReleaseP();
}
HRN_FORK_CHILD_END();

View File

@ -752,8 +752,8 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("555-fefefefe"), cfgOptionStr(cfgOptStanza), cfgLockType());
lockAcquireP(.timeout = 30000, .returnOnNoLock = true);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("555-fefefefe"));
cmdLockAcquireP(.returnOnNoLock = true);
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -761,7 +761,7 @@ testRun(void)
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
cmdLockReleaseP();
}
HRN_FORK_CHILD_END();

View File

@ -1862,15 +1862,15 @@ testRun(void)
uint64_t sizeProgress = 0;
currentPercentComplete = 4567;
lockInit(TEST_PATH_STR, cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), lockTypeBackup);
TEST_RESULT_VOID(lockAcquireP(), "acquire backup lock");
lockInit(TEST_PATH_STR, cfgOptionStr(cfgOptExecId));
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup lock");
TEST_RESULT_VOID(
backupJobResult(
manifest, STRDEF("host"), storageTest, strLstNew(), job, false, pgPageSize8, 0, &sizeProgress,
&currentPercentComplete),
"log noop result");
TEST_RESULT_VOID(lockRelease(true), "release backup lock");
TEST_RESULT_VOID(cmdLockReleaseP(), "release backup lock");
TEST_RESULT_LOG("P00 DETAIL: match file from prior backup host:" TEST_PATH "/test (0B, 100.00%)");
}

View File

@ -1,13 +1,15 @@
/***********************************************************************************************************************************
Test Command Control
***********************************************************************************************************************************/
#include "common/harnessConfig.h"
#include "common/harnessFork.h"
#include "common/harnessStorage.h"
#include "command/lock.h"
#include "common/io/fdRead.h"
#include "common/io/fdWrite.h"
#include "storage/posix/storage.h"
#include "common/harnessConfig.h"
#include "common/harnessFork.h"
#include "common/harnessStorage.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
@ -253,8 +255,9 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(STRDEF(HRN_PATH "/lock"), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), lockTypeArchive);
TEST_RESULT_BOOL(lockAcquireP(.timeout = 30000), true, "child process acquires lock");
String *lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive);
lockInit(STRDEF(HRN_PATH "/lock"), cfgOptionStr(cfgOptExecId));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create archive lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -339,6 +342,7 @@ testRun(void)
HRN_STORAGE_PUT_EMPTY(hrnStorage, "lock/db-archive" LOCK_FILE_EXT, .comment = "create empty archive lock file for stanza");
HRN_STORAGE_PUT_Z(hrnStorage, "lock/db1-backup" LOCK_FILE_EXT, " ", .comment = "create non-empty lock file other stanza");
lockInit(STRDEF(HRN_PATH "/lock"), STRDEF("1"));
TEST_RESULT_VOID(cmdStop(), "no stanza, create stop file, ignore non lock file");
TEST_STORAGE_EXISTS(hrnStorage, "lock/all" STOP_FILE_EXT, .comment = "stanza stop file created");
TEST_STORAGE_LIST(

View File

@ -236,8 +236,9 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"), STRDEF("stanza1"), lockTypeBackup);
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock");
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeBackup)), true, "create backup/expire lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -245,7 +246,7 @@ testRun(void)
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
lockReleaseP();
}
HRN_FORK_CHILD_END();
@ -428,9 +429,10 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("777-afafafaf"), STRDEF("stanza1"), lockTypeBackup);
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteDataP(lockTypeBackup), "write lock data");
String *lockFileName = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("777-afafafaf"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteP(lockFileName), "write lock data");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -438,7 +440,7 @@ testRun(void)
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
lockReleaseP();
}
HRN_FORK_CHILD_END();
@ -1037,11 +1039,12 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"), STRDEF("stanza2"), lockTypeBackup);
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock");
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(
lockWriteDataP(
lockTypeBackup, .percentComplete = VARUINT(4545), .sizeComplete = VARUINT64(1435765),
lockWriteP(
lockFileName, .percentComplete = VARUINT(4545), .sizeComplete = VARUINT64(1435765),
.size = VARUINT64(3159000)),
"write lock data");
@ -1051,7 +1054,7 @@ testRun(void)
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
lockReleaseP();
}
HRN_FORK_CHILD_END();
@ -1480,9 +1483,10 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"), STRDEF("stanza2"), lockTypeBackup);
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteDataP(lockTypeBackup, .percentComplete = VARUINT(5555)), "write lock data");
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteP(lockFileName, .percentComplete = VARUINT(5555)), "write lock data");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -1490,7 +1494,7 @@ testRun(void)
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true);
lockReleaseP();
}
HRN_FORK_CHILD_END();

View File

@ -0,0 +1,83 @@
/***********************************************************************************************************************************
Test Command Lock Handler
***********************************************************************************************************************************/
#include "storage/posix/storage.h"
#include "common/harnessConfig.h"
#include "common/harnessStorage.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
static void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// Create default storage object for testing
Storage *storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
// *****************************************************************************************************************************
if (testBegin("cmdLock*()"))
{
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire backup command lock");
StringList *argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1");
HRN_CFG_LOAD(cfgCmdBackup, argList, .noStd = true);
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup lock");
TEST_ERROR(cmdLockAcquireP(), AssertError, "lock is already held by this process");
TEST_STORAGE_LIST(storageTest, NULL, "test-backup.lock\n", .comment = "check lock file");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("write backup data");
TEST_RESULT_VOID(
cmdLockWriteP(.percentComplete = VARUINT(5555), .sizeComplete = VARUINT64(1754824), .size = VARUINT64(3159000)),
"write backup data");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("read backup data");
LockReadResult lockReadResult;
TEST_ASSIGN(lockReadResult, cmdLockRead(lockTypeBackup, STRDEF("test")), "read backup data");
TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
TEST_RESULT_UINT(varUInt(lockReadResult.data.percentComplete), 5555, "check percent complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.sizeComplete), 1754824, "check size complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.size), 3159000, "check size");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lock release");
TEST_RESULT_VOID(cmdLockReleaseP(), "release locks");
TEST_STORAGE_LIST(storageTest, NULL, NULL, .comment = "check lock file does not exist");
TEST_ERROR(cmdLockReleaseP(), AssertError, "no lock is held by this process");
TEST_RESULT_VOID(cmdLockReleaseP(.returnOnNoLock = true), "ignore no lock held");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire stanza-create command lock");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList, .noStd = true);
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire stanza-create lock");
TEST_STORAGE_LIST(storageTest, NULL, "test-archive.lock\ntest-backup.lock\n", .comment = "check lock file");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lock release");
TEST_RESULT_VOID(cmdLockReleaseP(), "release locks");
TEST_STORAGE_LIST(storageTest, NULL, NULL, .comment = "check lock file does not exist");
}
FUNCTION_HARNESS_RETURN_VOID();
}

View File

@ -1,7 +1,6 @@
/***********************************************************************************************************************************
Test Lock Handler
***********************************************************************************************************************************/
#include "common/time.h"
#include "storage/posix/storage.h"
#include "common/harnessFork.h"
@ -16,103 +15,108 @@ testRun(void)
FUNCTION_HARNESS_VOID();
// Create default storage object for testing
Storage *storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
const Storage *const storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
// *****************************************************************************************************************************
if (testBegin("lockAcquireFile() and lockReleaseFile()"))
if (testBegin("lock*()"))
{
const String *archiveLock = STRDEF(TEST_PATH "/main-archive" LOCK_FILE_EXT);
int lockFdTest = -1;
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire lock");
lockLocal.execId = STRDEF("1-test");
const String *const lockFile1Name = STRDEF("test1" LOCK_FILE_EXT);
TEST_ASSIGN(lockFdTest, lockAcquireFile(archiveLock, 0, true), "get lock");
TEST_RESULT_BOOL(lockFdTest != -1, true, "lock succeeds");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLock), true, "lock file was created");
TEST_RESULT_VOID(lockInit(TEST_PATH_STR, STRDEF("1-test")), "init lock module");
TEST_RESULT_BOOL(lockAcquireP(lockFile1Name), true, "acquire lock");
TEST_ERROR_FMT(lockAcquireP(lockFile1Name), AssertError, "lock on file 'test1.lock' already held");
lockLocal.file[lockTypeArchive].fd = lockFdTest;
lockLocal.file[lockTypeArchive].name = strDup(archiveLock);
TEST_RESULT_VOID(lockWriteDataP(lockTypeArchive), "write lock data");
lockLocal.execId = STRDEF("2-test");
TEST_ERROR_FMT(
lockAcquireFile(archiveLock, 0, true), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
strZ(archiveLock));
TEST_RESULT_BOOL(lockAcquireFile(archiveLock, 0, false) == -1, true, "lock is already held");
TEST_ERROR(lockAcquireP(TEST_PATH_STR), LockAcquireError, "unable to acquire lock on file '" TEST_PATH "': Is a directory");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire file lock on the same exec-id");
TEST_TITLE("read errors");
lockLocal.execId = STRDEF("1-test");
TEST_ERROR(
lockReadData(STRDEF(BOGUS_STR), 999999999), FileReadError,
"unable to read lock file 'BOGUS': unable to read from BOGUS: [9] Bad file descriptor");
TEST_RESULT_UINT(lockReadP(STRDEF(BOGUS_STR)).status, lockReadStatusMissing, "missing lock file");
TEST_RESULT_INT(lockAcquireFile(archiveLock, 0, true), -2, "allow lock with same exec id");
HRN_STORAGE_PUT_EMPTY(storageTest, "unlocked.lock");
TEST_RESULT_UINT(lockReadP(STRDEF("unlocked.lock"), .remove = true).status, lockReadStatusUnlocked, "unlocked lock file");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("fail file lock on the same exec-id when lock file is empty");
TEST_TITLE("read lock file (no data set)");
HRN_SYSTEM_FMT("echo '' > %s", strZ(archiveLock));
TEST_ERROR_FMT(
lockAcquireFile(archiveLock, 0, true), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
strZ(archiveLock));
LockReadResult lockReadResult;
TEST_ASSIGN(lockReadResult, lockReadP(lockFile1Name), "read lock file");
TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
TEST_RESULT_PTR(lockReadResult.data.percentComplete, NULL, "check percent complete is NULL");
TEST_RESULT_PTR(lockReadResult.data.sizeComplete, NULL, "check size complete is NULL");
TEST_RESULT_PTR(lockReadResult.data.size, NULL, "check size is NULL");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLock), "release lock");
TEST_TITLE("invalidate lock file by truncating");
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLock), "release lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLock), false, "lock file was removed");
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLock), "release lock again without error");
LockFile *lockFile1 = lstFind(lockLocal.lockList, &lockFile1Name);
THROW_ON_SYS_ERROR_FMT(
ftruncate(lockFile1->fd, 0) == -1, FileWriteError, "unable to truncate '" TEST_PATH "/%s'", strZ(lockFile1Name));
TEST_RESULT_UINT(lockReadP(lockFile1Name).status, lockReadStatusInvalid, "invalid lock file");
// -------------------------------------------------------------------------------------------------------------------------
String *subPathLock = strNewZ(TEST_PATH "/sub1/sub2/db-backup" LOCK_FILE_EXT);
TEST_TITLE("write errors");
TEST_ASSIGN(lockFdTest, lockAcquireFile(subPathLock, 0, true), "get lock in subpath");
TEST_RESULT_BOOL(storageExistsP(storageTest, subPathLock), true, "lock file was created");
TEST_RESULT_BOOL(lockFdTest != -1, true, "lock succeeds");
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, subPathLock), "release lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, subPathLock), false, "lock file was removed");
TEST_ERROR(lockWriteP(STRDEF(BOGUS_STR)), AssertError, "lock file 'BOGUS' not found");
// -------------------------------------------------------------------------------------------------------------------------
String *dirLock = strNewZ(TEST_PATH "/dir" LOCK_FILE_EXT);
TEST_TITLE("write lock file");
HRN_SYSTEM_FMT("mkdir -p %s", strZ(dirLock));
TEST_RESULT_VOID(
lockWriteP(
lockFile1Name, .percentComplete = VARUINT(5555), .sizeComplete = VARUINT64(1754824), .size = VARUINT64(3159000)),
"write lock data");
TEST_ERROR_FMT(
lockAcquireFile(dirLock, 0, true), LockAcquireError, "unable to acquire lock on file '%s': Is a directory",
strZ(dirLock));
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("read lock file (data set)");
TEST_ASSIGN(lockReadResult, lockReadP(lockFile1Name), "read lock file");
TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
TEST_RESULT_UINT(varUInt(lockReadResult.data.percentComplete), 5555, "check percent complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.sizeComplete), 1754824, "check size complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.size), 3159000, "check size");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("permissions error throw regardless of failOnLock");
String *noPermLock = strNewZ(TEST_PATH "/noperm/noperm");
const String *const noPermLock = strNewZ(TEST_PATH "/noperm/noperm");
HRN_SYSTEM_FMT("mkdir -p %s", strZ(strPath(noPermLock)));
HRN_SYSTEM_FMT("chmod 000 %s", strZ(strPath(noPermLock)));
TEST_ERROR_FMT(
lockAcquireFile(noPermLock, 100, true), LockAcquireError,
lockAcquireP(noPermLock), LockAcquireError,
"unable to acquire lock on file '%s': Permission denied\n"
"HINT: does '" TEST_USER ":" TEST_GROUP "' running pgBackRest have permissions on the '%s' file?",
strZ(noPermLock), strZ(noPermLock));
TEST_ERROR_FMT(
lockAcquireFile(noPermLock, 100, false), LockAcquireError,
lockAcquireP(noPermLock, .returnOnNoLock = true), LockAcquireError,
"unable to acquire lock on file '%s': Permission denied\n"
"HINT: does '" TEST_USER ":" TEST_GROUP "' running pgBackRest have permissions on the '%s' file?",
strZ(noPermLock), strZ(noPermLock));
// -------------------------------------------------------------------------------------------------------------------------
String *backupLock = strNewZ(TEST_PATH "/main-backup" LOCK_FILE_EXT);
TEST_TITLE("acquire file lock on the same exec-id");
const String *const lockFileExecName = STRDEF("sub/exec" LOCK_FILE_EXT);
const String *const lockFileExec2Name = STRDEF("sub/exec2" LOCK_FILE_EXT);
HRN_FORK_BEGIN()
{
HRN_FORK_CHILD_BEGIN()
{
TEST_RESULT_INT_NE(lockAcquireFile(backupLock, 0, true), -1, "lock on fork");
TEST_RESULT_BOOL(lockAcquireP(lockFileExecName), true, "lock on fork");
TEST_RESULT_BOOL(lockAcquireP(lockFileExec2Name), true, "lock on fork");
// Corrupt exec2.lock
HRN_SYSTEM_FMT("echo '' > " TEST_PATH "/%s", strZ(lockFileExec2Name));
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -130,237 +134,22 @@ testRun(void)
lockLocal.execId = STRDEF("2-test");
TEST_ERROR_FMT(
lockAcquireFile(backupLock, 0, true), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
lockAcquireP(lockFileExecName), LockAcquireError,
"unable to acquire lock on file '" TEST_PATH "/%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
strZ(backupLock));
strZ(lockFileExecName));
TEST_RESULT_VOID(lockAcquireFile(backupLock, 0, false), "success when failOnLock = false");
TEST_RESULT_BOOL(lockAcquireP(lockFileExecName, .returnOnNoLock = true), false, "fail but return");
// Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0);
}
HRN_FORK_PARENT_END();
}
HRN_FORK_END();
}
lockLocal.execId = STRDEF("1-test");
// *****************************************************************************************************************************
if (testBegin("lockAcquireP(), lockRelease()"))
{
const String *stanza = STRDEF("test");
String *archiveLockFile = strNewFmt(TEST_PATH "/%s-archive" LOCK_FILE_EXT, strZ(stanza));
String *backupLockFile = strNewFmt(TEST_PATH "/%s-backup" LOCK_FILE_EXT, strZ(stanza));
int lockFdTest = -1;
TEST_RESULT_BOOL(lockAcquireP(lockFileExecName), true, "succeed on same execId");
// -------------------------------------------------------------------------------------------------------------------------
TEST_ERROR(lockRelease(true), AssertError, "no lock is held by this process");
TEST_RESULT_BOOL(lockRelease(false), false, "release when there is no lock");
// -------------------------------------------------------------------------------------------------------------------------
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeArchive);
TEST_ASSIGN(lockFdTest, lockAcquireFile(archiveLockFile, 0, true), "archive lock by file");
TEST_RESULT_BOOL(lockAcquireP(.returnOnNoLock = true), false, "archive already locked");
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
strZ(archiveLockFile));
lockLocal.type = lockTypeAll;
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strZ(archiveLockFile));
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLockFile), "release lock");
// -------------------------------------------------------------------------------------------------------------------------
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeArchive);
TEST_RESULT_BOOL(lockAcquireP(), true, "archive lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLockFile), true, "archive lock file was created");
TEST_ERROR(lockAcquireP(.returnOnNoLock = true), AssertError, "lock is already held by this process");
TEST_RESULT_VOID(lockRelease(true), "release archive lock");
// -------------------------------------------------------------------------------------------------------------------------
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeBackup);
TEST_ASSIGN(lockFdTest, lockAcquireFile(backupLockFile, 0, true), "backup lock by file");
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strZ(backupLockFile));
lockLocal.type = lockTypeAll;
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strZ(backupLockFile));
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLockFile), "release archive lock");
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, backupLockFile), "release backup lock");
// -------------------------------------------------------------------------------------------------------------------------
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeAll);
TEST_RESULT_BOOL(lockAcquireP(), true, "all lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLockFile), true, "archive lock file was created");
TEST_RESULT_BOOL(storageExistsP(storageTest, backupLockFile), true, "backup lock file was created");
// Seek to start of file before read
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
TEST_RESULT_STR(
lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd).execId, STRDEF("1-test"), "verify execId");
TEST_RESULT_VOID(lockWriteDataP(lockTypeBackup), "write lock data");
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
TEST_RESULT_PTR(
lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd).percentComplete, NULL, "verify percentComplete");
TEST_RESULT_VOID(
lockWriteDataP(
lockTypeBackup, .percentComplete = VARUINT(5555), .sizeComplete = VARUINT64(1754824), .size = VARUINT64(3159000)),
"write lock data");
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
LockData lockDataResult = lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd);
TEST_RESULT_UINT(varUInt(lockDataResult.percentComplete), 5555, "verify percentComplete");
TEST_RESULT_UINT(varUInt64(lockDataResult.sizeComplete), 1754824, "verify sizeProgress");
TEST_RESULT_UINT(varUInt64(lockDataResult.size), 3159000, "verify sizeTotal");
// The size/sizeComplete values should be large enough to overflow a uint32 to ensure uint64 is used for read/write
TEST_RESULT_VOID(
lockWriteDataP(
lockTypeBackup, .percentComplete = VARUINT(8888), .sizeComplete = VARUINT64(3223802441008),
.size = VARUINT64(6126216975438)),
"write lock data");
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
lockDataResult = lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd);
TEST_RESULT_UINT(varUInt(lockDataResult.percentComplete), 8888, "verify percentComplete");
TEST_RESULT_UINT(varUInt64(lockDataResult.sizeComplete), 3223802441008, "verify sizeProgress");
TEST_RESULT_UINT(varUInt64(lockDataResult.size), 6126216975438, "verify sizeTotal");
TEST_ERROR(
lockAcquireP(.returnOnNoLock = true), AssertError,
"assertion '!param.returnOnNoLock || lockLocal.type != lockTypeAll' failed");
TEST_RESULT_VOID(lockRelease(true), "release all locks");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLockFile), false, "archive lock file was removed");
TEST_RESULT_BOOL(storageExistsP(storageTest, backupLockFile), false, "backup lock file was removed");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire lock on the same exec-id and release");
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "backup lock");
// Make it look there is no lock
lockFdTest = lockLocal.file[lockTypeBackup].fd;
String *lockFileTest = strDup(lockLocal.file[lockTypeBackup].name);
lockLocal.held = false;
TEST_RESULT_BOOL(lockAcquireP(), true, "backup lock again");
TEST_RESULT_VOID(lockRelease(true), "release backup lock");
// Release lock manually
lockReleaseFile(lockFdTest, lockFileTest);
}
// *****************************************************************************************************************************
if (testBegin("lockRead*()"))
{
TEST_TITLE("missing lock file");
TEST_RESULT_UINT(lockReadFileP(STRDEF(TEST_PATH "/missing.lock")).status, lockReadStatusMissing, "lock read");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("unlocked file");
HRN_STORAGE_PUT_EMPTY(storageTest, "unlocked.lock");
TEST_RESULT_UINT(lockReadFileP(STRDEF(TEST_PATH "/unlocked.lock")).status, lockReadStatusUnlocked, "lock read");
TEST_STORAGE_LIST(storageTest, NULL, "unlocked.lock\n", .remove = true);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("execId && pid valid file");
lockInit(TEST_PATH_STR, STRDEF("1-test"), STRDEF("1-test"), lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "backup lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, lockLocal.file[lockTypeBackup].name), true, "backup lock file was created");
// Overwrite backup lock file with execId of 1-test and pid of 12345
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
TEST_RESULT_INT(ftruncate(lockLocal.file[lockTypeBackup].fd, 0), 0, "truncate lockLocal.file[lockTypeBackup].fd");
IoWrite *const write = ioFdWriteNewOpen(lockLocal.file[lockTypeBackup].name, lockLocal.file[lockTypeBackup].fd, 0);
ioCopyP(ioBufferReadNewOpen(BUFSTRDEF("{\"execId\":\"1-test\",\"pid\":12345}")), write);
ioWriteClose(write);
// Seek to start of file before read
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
LockReadResult result = {0};
TEST_ASSIGN(result, lockReadFileP(lockLocal.file[lockTypeBackup].name), "lock read");
TEST_RESULT_STR(result.data.execId, STRDEF("1-test"), "lock read execId 1-test");
TEST_RESULT_UINT((uint64_t)result.data.processId, 12345, "lock read pid 12345");
TEST_RESULT_VOID(lockRelease(true), "release backup lock");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("invalid locked file");
HRN_FORK_BEGIN()
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(TEST_PATH_STR, STRDEF("1-test"), STRDEF("test"), lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "acquire lock");
// Overwrite lock file with bogus data
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK,
(uint64_t)0, strZ(lockLocal.file[lockTypeBackup].name));
IoWrite *const write = ioFdWriteNewOpen(lockLocal.file[lockTypeBackup].name, lockLocal.file[lockTypeBackup].fd, 0);
ioCopyP(ioBufferReadNewOpen(BUFSTRDEF("BOGUS")), write);
ioWriteClose(write);
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
}
HRN_FORK_CHILD_END();
HRN_FORK_PARENT_BEGIN()
{
// Wait for child to acquire lock
HRN_FORK_PARENT_NOTIFY_GET(0);
TEST_RESULT_UINT(
lockReadFileP(STRDEF(TEST_PATH "/test-backup.lock"), .remove = true).status, lockReadStatusInvalid,
"lock read");
TEST_STORAGE_LIST(storageTest, NULL, NULL);
TEST_ERROR_FMT(
lockAcquireP(lockFileExec2Name), LockAcquireError,
"unable to acquire lock on file '" TEST_PATH "/%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
strZ(lockFileExec2Name));
// Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0);
@ -370,43 +159,11 @@ testRun(void)
HRN_FORK_END();
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("valid locked file");
TEST_TITLE("lock release");
HRN_FORK_BEGIN()
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(TEST_PATH_STR, STRDEF("1-test"), STRDEF("test"), lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "acquire lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
}
HRN_FORK_CHILD_END();
HRN_FORK_PARENT_BEGIN()
{
// Wait for child to acquire lock
HRN_FORK_PARENT_NOTIFY_GET(0);
LockReadResult result = {0};
TEST_ASSIGN(result, lockRead(TEST_PATH_STR, STRDEF("test"), lockTypeBackup), "lock read");
TEST_RESULT_BOOL(result.data.processId != 0, true, "check processId");
TEST_RESULT_STR_Z(result.data.execId, "1-test", "check execId");
TEST_STORAGE_LIST(storageTest, NULL, "test-backup.lock\n");
// Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0);
}
HRN_FORK_PARENT_END();
}
HRN_FORK_END();
TEST_RESULT_VOID(lockReleaseP(), "release locks");
TEST_ERROR(lockReleaseP(), AssertError, "no lock is held by this process");
TEST_RESULT_VOID(lockReleaseP(.returnOnNoLock = true), "ignore no lock held");
}
FUNCTION_HARNESS_RETURN_VOID();

View File

@ -755,7 +755,7 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptLogLevelStderr, CFGOPTVAL_ARCHIVE_MODE_OFF_Z);
strLstAddZ(argList, CFGCMD_BACKUP);
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for backup");
lockRelease(true);
cmdLockReleaseP();
// Only the error case is tested here, success is tested in cfgLoad()
TEST_RESULT_VOID(cfgLoadLogFile(), "attempt to open bogus log file");
@ -777,7 +777,7 @@ testRun(void)
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config");
TEST_RESULT_VOID(storageRepoWrite(), "check writable storage");
lockRelease(true);
cmdLockReleaseP();
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("dry-run valid, dry-run");
@ -787,7 +787,7 @@ testRun(void)
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config");
TEST_ERROR(
storageRepoWrite(), AssertError, "unable to get writable storage in dry-run mode or before dry-run is initialized");
lockRelease(true);
cmdLockReleaseP();
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("command does not have umask and disables keep-alives");
@ -928,7 +928,7 @@ testRun(void)
TEST_RESULT_INT(socketLocal.tcpKeepAliveIdle, 2222, "check socketLocal.tcpKeepAliveIdle");
TEST_RESULT_INT(socketLocal.tcpKeepAliveInterval, 888, "check socketLocal.tcpKeepAliveInterval");
lockRelease(true);
cmdLockReleaseP();
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("local command opens log file with special filename");
@ -1011,7 +1011,7 @@ testRun(void)
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "open log file");
TEST_RESULT_INT(lstat(TEST_PATH "/test-archive-get-async.log", &statLog), 0, "check log file exists");
lockRelease(true);
cmdLockReleaseP();
}
FUNCTION_HARNESS_RETURN_VOID();