mirror of
https://github.com/borgbackup/borg.git
synced 2026-04-23 07:07:52 -04:00
Parse & pass BORG_HOSTNAME_IS_UNIQUE env var to enable stale lock killing
This commit is contained in:
parent
cc14975f2d
commit
676e69cac4
6 changed files with 27 additions and 16 deletions
|
|
@ -75,7 +75,9 @@ class Cache:
|
|||
self.key = key
|
||||
self.manifest = manifest
|
||||
self.path = path or os.path.join(get_cache_dir(), repository.id_str)
|
||||
self.unique_hostname = bool(os.environ.get('BORG_UNIQUE_HOSTNAME'))
|
||||
self.hostname_is_unique = yes(env_var_override='BORG_HOSTNAME_IS_UNIQUE', prompt=False, env_msg=None)
|
||||
if self.hostname_is_unique:
|
||||
logger.info('Enabled removal of stale cache locks')
|
||||
self.do_files = do_files
|
||||
# Warn user before sending data to a never seen before unencrypted repository
|
||||
if not os.path.exists(self.path):
|
||||
|
|
@ -203,7 +205,7 @@ Chunk index: {0.total_unique_chunks:20d} {0.total_chunks:20d}"""
|
|||
def open(self, lock_wait=None):
|
||||
if not os.path.isdir(self.path):
|
||||
raise Exception('%s Does not look like a Borg cache' % self.path)
|
||||
self.lock = Lock(os.path.join(self.path, 'lock'), exclusive=True, timeout=lock_wait, kill_stale_locks=self.unique_hostname).acquire()
|
||||
self.lock = Lock(os.path.join(self.path, 'lock'), exclusive=True, timeout=lock_wait, kill_stale_locks=self.hostname_is_unique).acquire()
|
||||
self.rollback()
|
||||
|
||||
def close(self):
|
||||
|
|
|
|||
|
|
@ -1116,7 +1116,7 @@ DEFAULTISH = ('Default', 'DEFAULT', 'default', 'D', 'd', '', )
|
|||
def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
|
||||
retry_msg=None, invalid_msg=None, env_msg='{} (from {})',
|
||||
falsish=FALSISH, truish=TRUISH, defaultish=DEFAULTISH,
|
||||
default=False, retry=True, env_var_override=None, ofile=None, input=input):
|
||||
default=False, retry=True, env_var_override=None, ofile=None, input=input, prompt=True):
|
||||
"""Output <msg> (usually a question) and let user input an answer.
|
||||
Qualifies the answer according to falsish, truish and defaultish as True, False or <default>.
|
||||
If it didn't qualify and retry is False (no retries wanted), return the default [which
|
||||
|
|
@ -1161,6 +1161,8 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
|
|||
if answer is not None and env_msg:
|
||||
print(env_msg.format(answer, env_var_override), file=ofile)
|
||||
if answer is None:
|
||||
if not prompt:
|
||||
return default
|
||||
try:
|
||||
answer = input()
|
||||
except EOFError:
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class ExclusiveLock:
|
|||
self.path = os.path.abspath(path)
|
||||
self.id = id or platform.get_process_id()
|
||||
self.unique_name = os.path.join(self.path, "%s.%d-%x" % self.id)
|
||||
self.ok_to_kill_stale_locks = kill_stale_locks
|
||||
self.kill_stale_locks = kill_stale_locks
|
||||
self.stale_warning_printed = False
|
||||
|
||||
def __enter__(self):
|
||||
|
|
@ -163,16 +163,16 @@ class ExclusiveLock:
|
|||
thread = int(thread_str)
|
||||
except ValueError:
|
||||
# Malformed lock name? Or just some new format we don't understand?
|
||||
# It's safer to just exit
|
||||
# It's safer to just exit.
|
||||
return False
|
||||
|
||||
if platform.process_alive(host, pid, thread):
|
||||
return False
|
||||
|
||||
if not self.ok_to_kill_stale_locks:
|
||||
if not self.kill_stale_locks:
|
||||
if not self.stale_warning_printed:
|
||||
# Log this at warning level to hint the user at the ability
|
||||
logger.warning("Found stale lock %s, but not deleting because BORG_UNIQUE_HOSTNAME is not set.", name)
|
||||
logger.warning("Found stale lock %s, but not deleting because BORG_HOSTNAME_IS_UNIQUE is not set.", name)
|
||||
self.stale_warning_printed = True
|
||||
return False
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ class LockRoster:
|
|||
def __init__(self, path, id=None, kill_stale_locks=False):
|
||||
self.path = path
|
||||
self.id = id or platform.get_process_id()
|
||||
self.ok_to_kill_zombie_locks = kill_stale_locks
|
||||
self.kill_stale_locks = kill_stale_locks
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
|
|
@ -221,7 +221,7 @@ class LockRoster:
|
|||
data = json.load(f)
|
||||
|
||||
# Just nuke the stale locks early on load
|
||||
if self.ok_to_kill_zombie_locks:
|
||||
if self.kill_stale_locks:
|
||||
for key in (SHARED, EXCLUSIVE):
|
||||
try:
|
||||
entries = data[key]
|
||||
|
|
@ -237,7 +237,6 @@ class LockRoster:
|
|||
except (FileNotFoundError, ValueError):
|
||||
# no or corrupt/empty roster file?
|
||||
data = {}
|
||||
|
||||
return data
|
||||
|
||||
def save(self, data):
|
||||
|
|
|
|||
|
|
@ -53,12 +53,13 @@ def process_alive(host, pid, thread):
|
|||
|
||||
return local_pid_alive(pid)
|
||||
|
||||
|
||||
def local_pid_alive(pid):
|
||||
"""Return whether *pid* is alive."""
|
||||
try:
|
||||
# This doesn't work on Windows.
|
||||
# This does not kill anything, 0 means "see if we can send a signal to this process or not".
|
||||
# Possible errors: No such process (== stale lock) or permission denied (not a stale lock)
|
||||
# Possible errors: No such process (== stale lock) or permission denied (not a stale lock).
|
||||
# If the exception is not raised that means such a pid is valid and we can send a signal to it.
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
|
|
@ -66,5 +67,5 @@ def local_pid_alive(pid):
|
|||
if err.errno == errno.ESRCH:
|
||||
# ESRCH = no such process
|
||||
return False
|
||||
# Any other error (eg. permissions) mean that the process ID refers to a live process
|
||||
# Any other error (eg. permissions) means that the process ID refers to a live process.
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from .helpers import get_home_dir
|
|||
from .helpers import sysinfo
|
||||
from .helpers import bin_to_hex
|
||||
from .helpers import replace_placeholders
|
||||
from .helpers import yes
|
||||
from .repository import Repository
|
||||
|
||||
RPC_PROTOCOL_VERSION = 2
|
||||
|
|
@ -326,12 +327,15 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
|||
opts.append('--critical')
|
||||
else:
|
||||
raise ValueError('log level missing, fix this code')
|
||||
env_vars = []
|
||||
if yes(env_var_override='BORG_HOSTNAME_IS_UNIQUE', env_msg=None, prompt=False):
|
||||
env_vars.append('BORG_HOSTNAME_IS_UNIQUE=yes')
|
||||
if testing:
|
||||
return [sys.executable, '-m', 'borg.archiver', 'serve'] + opts + self.extra_test_args
|
||||
return env_vars + [sys.executable, '-m', 'borg.archiver', 'serve'] + opts + self.extra_test_args
|
||||
else: # pragma: no cover
|
||||
remote_path = args.remote_path or os.environ.get('BORG_REMOTE_PATH', 'borg')
|
||||
remote_path = replace_placeholders(remote_path)
|
||||
return [remote_path, 'serve'] + opts
|
||||
return env_vars + [remote_path, 'serve'] + opts
|
||||
|
||||
def ssh_cmd(self, location):
|
||||
"""return a ssh command line that can be prefixed to a borg command line"""
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from .helpers import Error, ErrorWithTraceback, IntegrityError, format_file_size
|
|||
from .helpers import Location
|
||||
from .helpers import ProgressIndicatorPercent
|
||||
from .helpers import bin_to_hex
|
||||
from .helpers import yes
|
||||
from .locking import Lock, LockError, LockErrorT
|
||||
from .logger import create_logger
|
||||
from .lrucache import LRUCache
|
||||
|
|
@ -121,7 +122,9 @@ class Repository:
|
|||
self.do_create = create
|
||||
self.exclusive = exclusive
|
||||
self.append_only = append_only
|
||||
self.unique_hostname = bool(os.environ.get('BORG_UNIQUE_HOSTNAME'))
|
||||
self.hostname_is_unique = yes(env_var_override='BORG_HOSTNAME_IS_UNIQUE', env_msg=None, prompt=False)
|
||||
if self.hostname_is_unique:
|
||||
logger.info('Enabled removal of stale repository locks')
|
||||
|
||||
def __del__(self):
|
||||
if self.lock:
|
||||
|
|
@ -255,7 +258,7 @@ class Repository:
|
|||
if not os.path.isdir(path):
|
||||
raise self.DoesNotExist(path)
|
||||
if lock:
|
||||
self.lock = Lock(os.path.join(path, 'lock'), exclusive, timeout=lock_wait, kill_stale_locks=self.unique_hostname).acquire()
|
||||
self.lock = Lock(os.path.join(path, 'lock'), exclusive, timeout=lock_wait, kill_stale_locks=self.hostname_is_unique).acquire()
|
||||
else:
|
||||
self.lock = None
|
||||
self.config = ConfigParser(interpolation=None)
|
||||
|
|
|
|||
Loading…
Reference in a new issue