mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-03-05 15:05:48 +02:00
More modular locking - this is the first part of a series of improvements.
This commit is contained in:
parent
87f69508e3
commit
2dac171642
@ -19,6 +19,7 @@ use BackRest::Config;
|
|||||||
use BackRest::Remote qw(DB BACKUP NONE);
|
use BackRest::Remote qw(DB BACKUP NONE);
|
||||||
use BackRest::Db;
|
use BackRest::Db;
|
||||||
use BackRest::File;
|
use BackRest::File;
|
||||||
|
use BackRest::Lock;
|
||||||
use BackRest::Archive;
|
use BackRest::Archive;
|
||||||
use BackRest::Backup;
|
use BackRest::Backup;
|
||||||
use BackRest::Restore;
|
use BackRest::Restore;
|
||||||
@ -77,7 +78,7 @@ pg_backrest.pl [options] [operation]
|
|||||||
=cut
|
=cut
|
||||||
|
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
# SAFE_EXIT - terminate all SSH sessions when the script is terminated
|
# SAFE_EXIT - terminate all threads and SSH connections when the script is terminated
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
sub safe_exit
|
sub safe_exit
|
||||||
{
|
{
|
||||||
@ -124,6 +125,11 @@ if (operationTest(OP_ARCHIVE_PUSH) || operationTest(OP_ARCHIVE_GET))
|
|||||||
safe_exit(new BackRest::Archive()->process());
|
safe_exit(new BackRest::Archive()->process());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
####################################################################################################################################
|
||||||
|
# Acquire the operation lock
|
||||||
|
####################################################################################################################################
|
||||||
|
lockAcquire(operationGet());
|
||||||
|
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
# Open the log file
|
# Open the log file
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
@ -155,10 +161,6 @@ if (operationTest(OP_RESTORE))
|
|||||||
confess &log(ASSERT, 'restore operation must be performed locally on the db server');
|
confess &log(ASSERT, 'restore operation must be performed locally on the db server');
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set the lock path
|
|
||||||
my $strLockPath = optionGet(OPTION_REPO_PATH) . '/lock/' .
|
|
||||||
optionGet(OPTION_STANZA) . '-' . operationGet() . '.lock';
|
|
||||||
|
|
||||||
# Do the restore
|
# Do the restore
|
||||||
new BackRest::Restore
|
new BackRest::Restore
|
||||||
(
|
(
|
||||||
@ -192,15 +194,6 @@ if (optionRemoteTypeTest(BACKUP))
|
|||||||
confess &log(ERROR, 'backup and expire operations must run on the backup host');
|
confess &log(ERROR, 'backup and expire operations must run on the backup host');
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set the lock path
|
|
||||||
my $strLockPath = optionGet(OPTION_REPO_PATH) . '/lock/' . optionGet(OPTION_STANZA) . '-' . operationGet() . '.lock';
|
|
||||||
|
|
||||||
if (!lock_file_create($strLockPath))
|
|
||||||
{
|
|
||||||
&log(ERROR, 'backup process is already running for stanza ' . optionGet(OPTION_STANZA) . ' - exiting');
|
|
||||||
safe_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Initialize the db object
|
# Initialize the db object
|
||||||
my $oDb;
|
my $oDb;
|
||||||
|
|
||||||
@ -264,11 +257,14 @@ if (operationTest(OP_EXPIRE))
|
|||||||
optionGet(OPTION_RETENTION_ARCHIVE_TYPE, false),
|
optionGet(OPTION_RETENTION_ARCHIVE_TYPE, false),
|
||||||
optionGet(OPTION_RETENTION_ARCHIVE, false)
|
optionGet(OPTION_RETENTION_ARCHIVE, false)
|
||||||
);
|
);
|
||||||
|
|
||||||
lock_file_remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Cleanup backup (should be removed when backup becomes an object)
|
||||||
backup_cleanup();
|
backup_cleanup();
|
||||||
|
|
||||||
|
# Release the operation lock
|
||||||
|
lockRelease();
|
||||||
|
|
||||||
safe_exit(0);
|
safe_exit(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ use lib dirname($0);
|
|||||||
use BackRest::Utility;
|
use BackRest::Utility;
|
||||||
use BackRest::Exception;
|
use BackRest::Exception;
|
||||||
use BackRest::Config;
|
use BackRest::Config;
|
||||||
|
use BackRest::Lock;
|
||||||
use BackRest::File;
|
use BackRest::File;
|
||||||
use BackRest::Remote;
|
use BackRest::Remote;
|
||||||
|
|
||||||
@ -397,13 +398,7 @@ sub pushProcess
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Create a lock file to make sure async archive-push does not run more than once
|
# Create a lock file to make sure async archive-push does not run more than once
|
||||||
my $strLockPath = "${strArchivePath}/lock/" . optionGet(OPTION_STANZA) . "-archive.lock";
|
lockAcquire(operationGet());
|
||||||
|
|
||||||
if (!lock_file_create($strLockPath))
|
|
||||||
{
|
|
||||||
&log(DEBUG, 'archive-push process is already running - exiting');
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Open the log file
|
# Open the log file
|
||||||
log_file_set(optionGet(OPTION_REPO_PATH) . '/log/' . optionGet(OPTION_STANZA) . '-archive-async');
|
log_file_set(optionGet(OPTION_REPO_PATH) . '/log/' . optionGet(OPTION_STANZA) . '-archive-async');
|
||||||
@ -428,7 +423,7 @@ sub pushProcess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lock_file_remove();
|
lockRelease();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,14 +34,15 @@ use constant
|
|||||||
ERROR_PARAM_REQUIRED => 118,
|
ERROR_PARAM_REQUIRED => 118,
|
||||||
ERROR_ARCHIVE_MISMATCH => 119,
|
ERROR_ARCHIVE_MISMATCH => 119,
|
||||||
ERROR_ARCHIVE_DUPLICATE => 120,
|
ERROR_ARCHIVE_DUPLICATE => 120,
|
||||||
ERROR_VERSION_NOT_SUPPORTED => 121
|
ERROR_VERSION_NOT_SUPPORTED => 121,
|
||||||
|
ERROR_PATH_CREATE => 122
|
||||||
};
|
};
|
||||||
|
|
||||||
our @EXPORT = qw(ERROR_ASSERT ERROR_CHECKSUM ERROR_CONFIG ERROR_FILE_INVALID ERROR_FORMAT ERROR_OPERATION_REQUIRED
|
our @EXPORT = qw(ERROR_ASSERT ERROR_CHECKSUM ERROR_CONFIG ERROR_FILE_INVALID ERROR_FORMAT ERROR_OPERATION_REQUIRED
|
||||||
ERROR_OPTION_INVALID ERROR_OPTION_INVALID_VALUE ERROR_OPTION_INVALID_RANGE ERROR_OPTION_INVALID_PAIR
|
ERROR_OPTION_INVALID ERROR_OPTION_INVALID_VALUE ERROR_OPTION_INVALID_RANGE ERROR_OPTION_INVALID_PAIR
|
||||||
ERROR_OPTION_DUPLICATE_KEY ERROR_OPTION_NEGATE ERROR_OPTION_REQUIRED ERROR_POSTMASTER_RUNNING ERROR_PROTOCOL
|
ERROR_OPTION_DUPLICATE_KEY ERROR_OPTION_NEGATE ERROR_OPTION_REQUIRED ERROR_POSTMASTER_RUNNING ERROR_PROTOCOL
|
||||||
ERROR_RESTORE_PATH_NOT_EMPTY ERROR_FILE_OPEN ERROR_FILE_READ ERROR_PARAM_REQUIRED ERROR_ARCHIVE_MISMATCH
|
ERROR_RESTORE_PATH_NOT_EMPTY ERROR_FILE_OPEN ERROR_FILE_READ ERROR_PARAM_REQUIRED ERROR_ARCHIVE_MISMATCH
|
||||||
ERROR_ARCHIVE_DUPLICATE ERROR_VERSION_NOT_SUPPORTED);
|
ERROR_ARCHIVE_DUPLICATE ERROR_VERSION_NOT_SUPPORTED ERROR_PATH_CREATE);
|
||||||
|
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
# CONSTRUCTOR
|
# CONSTRUCTOR
|
||||||
|
127
lib/BackRest/Lock.pm
Normal file
127
lib/BackRest/Lock.pm
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
####################################################################################################################################
|
||||||
|
# LOCK MODULE
|
||||||
|
####################################################################################################################################
|
||||||
|
package BackRest::Lock;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => qw(all);
|
||||||
|
use Carp qw(confess);
|
||||||
|
|
||||||
|
use Fcntl qw(:DEFAULT :flock);
|
||||||
|
use File::Basename qw(dirname);
|
||||||
|
use Exporter qw(import);
|
||||||
|
|
||||||
|
use lib dirname($0) . '/../lib';
|
||||||
|
use BackRest::Exception;
|
||||||
|
use BackRest::Config;
|
||||||
|
use BackRest::Utility;
|
||||||
|
|
||||||
|
####################################################################################################################################
|
||||||
|
# Exported Functions
|
||||||
|
####################################################################################################################################
|
||||||
|
our @EXPORT = qw(lockAcquire lockRelease);
|
||||||
|
|
||||||
|
####################################################################################################################################
|
||||||
|
# Global lock type and handle
|
||||||
|
####################################################################################################################################
|
||||||
|
my $strCurrentLockType;
|
||||||
|
my $strCurrentLockFile;
|
||||||
|
my $hCurrentLockHandle;
|
||||||
|
|
||||||
|
####################################################################################################################################
|
||||||
|
# lockPathName
|
||||||
|
#
|
||||||
|
# Get the base path where the lock file will be stored.
|
||||||
|
####################################################################################################################################
|
||||||
|
sub lockPathName
|
||||||
|
{
|
||||||
|
my $strRepoPath = shift;
|
||||||
|
|
||||||
|
return "${strRepoPath}/lock";
|
||||||
|
}
|
||||||
|
|
||||||
|
####################################################################################################################################
|
||||||
|
# lockFileName
|
||||||
|
#
|
||||||
|
# Get the lock file name.
|
||||||
|
####################################################################################################################################
|
||||||
|
sub lockFileName
|
||||||
|
{
|
||||||
|
my $strLockType = shift;
|
||||||
|
my $strStanza = shift;
|
||||||
|
my $strRepoPath = shift;
|
||||||
|
|
||||||
|
return lockPathName($strRepoPath) . "/${strStanza}-${strLockType}.lock";
|
||||||
|
}
|
||||||
|
|
||||||
|
####################################################################################################################################
|
||||||
|
# lockAcquire
|
||||||
|
#
|
||||||
|
# Attempt to acquire the specified lock. If the lock is taken by another process return false, else take the lock and return true.
|
||||||
|
####################################################################################################################################
|
||||||
|
sub lockAcquire
|
||||||
|
{
|
||||||
|
my $strLockType = shift;
|
||||||
|
|
||||||
|
# Cannot proceed if a lock is currently held
|
||||||
|
if (defined($strCurrentLockType))
|
||||||
|
{
|
||||||
|
confess &lock(ASSERT, "${strCurrentLockType} lock is already held");
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create the lock path if it does not exist
|
||||||
|
if (! -e lockPathName(optionGet(OPTION_REPO_PATH)))
|
||||||
|
{
|
||||||
|
mkdir lockPathName(optionGet(OPTION_REPO_PATH))
|
||||||
|
or confess(ERROR, 'unable to create lock path ' . lockPathName(optionGet(OPTION_REPO_PATH)), ERROR_PATH_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attempt to open the lock file
|
||||||
|
$strCurrentLockFile = lockFileName($strLockType, optionGet(OPTION_STANZA), optionGet(OPTION_REPO_PATH));
|
||||||
|
|
||||||
|
sysopen($hCurrentLockHandle, $strCurrentLockFile, O_WRONLY | O_CREAT)
|
||||||
|
or confess &log(ERROR, "unable to open lock file ${strCurrentLockFile}");
|
||||||
|
|
||||||
|
# Attempt to lock the lock file
|
||||||
|
if (!flock($hCurrentLockHandle, LOCK_EX | LOCK_NB))
|
||||||
|
{
|
||||||
|
close($hCurrentLockHandle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set current lock type so we know we have a lock
|
||||||
|
$strCurrentLockType = $strLockType;
|
||||||
|
|
||||||
|
# Lock was successful
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
####################################################################################################################################
|
||||||
|
# lockRelease
|
||||||
|
####################################################################################################################################
|
||||||
|
sub lockRelease
|
||||||
|
{
|
||||||
|
my $strLockType = shift;
|
||||||
|
|
||||||
|
# Fail if there is no lock
|
||||||
|
if (!defined($strCurrentLockType))
|
||||||
|
{
|
||||||
|
confess &log(ASSERT, 'no lock is currently held');
|
||||||
|
}
|
||||||
|
|
||||||
|
# # Fail if the lock being released is not the one held
|
||||||
|
# if ($strLockType ne $strCurrentLockType)
|
||||||
|
# {
|
||||||
|
# confess &log(ASSERT, "cannot remove lock ${strLockType} since ${strCurrentLockType} is currently held");
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Remove the file
|
||||||
|
unlink($strCurrentLockFile);
|
||||||
|
close($hCurrentLockHandle);
|
||||||
|
|
||||||
|
# Undef lock variables
|
||||||
|
undef($strCurrentLockType);
|
||||||
|
undef($hCurrentLockHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
@ -24,7 +24,7 @@ use Exporter qw(import);
|
|||||||
our @EXPORT = qw(version_get
|
our @EXPORT = qw(version_get
|
||||||
data_hash_build trim common_prefix file_size_format execute
|
data_hash_build trim common_prefix file_size_format execute
|
||||||
log log_file_set log_level_set test_set test_get test_check
|
log log_file_set log_level_set test_set test_get test_check
|
||||||
lock_file_create lock_file_remove hsleep wait_remainder
|
hsleep wait_remainder
|
||||||
ini_save ini_load timestamp_string_get timestamp_file_string_get
|
ini_save ini_load timestamp_string_get timestamp_file_string_get
|
||||||
TRACE DEBUG ERROR ASSERT WARN INFO OFF true false
|
TRACE DEBUG ERROR ASSERT WARN INFO OFF true false
|
||||||
TEST TEST_ENCLOSE TEST_MANIFEST_BUILD TEST_BACKUP_RESUME TEST_BACKUP_NORESUME FORMAT);
|
TEST TEST_ENCLOSE TEST_MANIFEST_BUILD TEST_BACKUP_RESUME TEST_BACKUP_NORESUME FORMAT);
|
||||||
@ -125,62 +125,6 @@ sub version_get
|
|||||||
return $strVersion;
|
return $strVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
####################################################################################################################################
|
|
||||||
# LOCK_FILE_CREATE
|
|
||||||
####################################################################################################################################
|
|
||||||
sub lock_file_create
|
|
||||||
{
|
|
||||||
my $strLockPathParam = shift;
|
|
||||||
|
|
||||||
my $strLockFile = $strLockPathParam . '/process.lock';
|
|
||||||
|
|
||||||
if (defined($hLockFile))
|
|
||||||
{
|
|
||||||
confess &lock(ASSERT, "${strLockFile} lock is already held");
|
|
||||||
}
|
|
||||||
|
|
||||||
$strLockPath = $strLockPathParam;
|
|
||||||
|
|
||||||
unless (-e $strLockPath)
|
|
||||||
{
|
|
||||||
if (system("mkdir -p ${strLockPath}") != 0)
|
|
||||||
{
|
|
||||||
confess &log(ERROR, "Unable to create lock path ${strLockPath}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sysopen($hLockFile, $strLockFile, O_WRONLY | O_CREAT)
|
|
||||||
or confess &log(ERROR, "unable to open lock file ${strLockFile}");
|
|
||||||
|
|
||||||
if (!flock($hLockFile, LOCK_EX | LOCK_NB))
|
|
||||||
{
|
|
||||||
close($hLockFile);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $hLockFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
####################################################################################################################################
|
|
||||||
# LOCK_FILE_REMOVE
|
|
||||||
####################################################################################################################################
|
|
||||||
sub lock_file_remove
|
|
||||||
{
|
|
||||||
if (defined($hLockFile))
|
|
||||||
{
|
|
||||||
close($hLockFile);
|
|
||||||
|
|
||||||
remove_tree($strLockPath) or confess &log(ERROR, "unable to delete lock path ${strLockPath}");
|
|
||||||
|
|
||||||
$hLockFile = undef;
|
|
||||||
$strLockPath = undef;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
confess &log(ASSERT, 'there is no lock to free');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
# WAIT_REMAINDER - Wait the remainder of the current second
|
# WAIT_REMAINDER - Wait the remainder of the current second
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
|
Loading…
x
Reference in New Issue
Block a user