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:
parent
ad7377c75b
commit
270dce41b6
@ -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();
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
||||
|
@ -27,5 +27,18 @@
|
||||
<p>Allow alternative WAL segment sizes for PostgreSQL &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>
|
||||
|
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
149
src/command/lock.c
Normal 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
35
src/command/lock.h
Normal 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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
¤tPercentComplete),
|
||||
"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%)");
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
||||
|
83
test/src/module/command/lockTest.c
Normal file
83
test/src/module/command/lockTest.c
Normal 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();
|
||||
}
|
@ -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();
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user