From c562f7750c80c71976c919dab62e6a9d4d27db2b Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Sat, 1 Oct 2016 18:33:41 +0200 Subject: [PATCH] Move platform-dependent code to platform package --- src/borg/locking.py | 49 ++++--------------------------- src/borg/platform/__init__.py | 1 + src/borg/platform/posix.pyx | 55 +++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 43 deletions(-) diff --git a/src/borg/locking.py b/src/borg/locking.py index 56f280929..02c1c758a 100644 --- a/src/borg/locking.py +++ b/src/borg/locking.py @@ -6,47 +6,11 @@ import sys import time from .helpers import Error, ErrorWithTraceback +from .platform import process_alive, get_process_id ADD, REMOVE = 'add', 'remove' SHARED, EXCLUSIVE = 'shared', 'exclusive' -# only determine the PID and hostname once. -# for FUSE mounts, we fork a child process that needs to release -# the lock made by the parent, so it needs to use the same PID for that. -_pid = os.getpid() -_hostname = socket.gethostname() - - -def get_id(): - """Get identification tuple for 'us'""" - - # If changing the thread_id to ever be non-zero, also revisit the check_lock_stale() below. - thread_id = 0 - return _hostname, _pid, thread_id - - -def check_lock_stale(host, pid, thread): - """Check if the host, pid, thread combination corresponds to a dead process on our local node or not.""" - if host != _hostname: - return False - - if thread != 0: - # Currently thread is always 0, if we ever decide to set this to a non-zero value, this code needs to be revisited too to do a sensible thing - return False - - try: - # This may not work in 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) - # If the exception is not raised that means such a pid is valid and we can send a signal to it (== not a stale lock too). - os.kill(pid, 0) - return False - except OSError as err: - if err.errno != errno.ESRCH: - return False - pass - - return True class TimeoutTimer: @@ -141,7 +105,7 @@ class ExclusiveLock: self.timeout = timeout self.sleep = sleep self.path = os.path.abspath(path) - self.id = id or get_id() + self.id = id or 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.stale_warning_printed = False @@ -194,7 +158,6 @@ class ExclusiveLock: def kill_stale_lock(self): for name in os.listdir(self.path): - try: host_pid, thread_str = name.rsplit('-', 1) host, pid_str = host_pid.rsplit('.', 1) @@ -205,7 +168,7 @@ class ExclusiveLock: # It's safer to just exit return False - if not check_lock_stale(host, pid, thread): + if not process_alive(host, pid, thread): return False if not self.ok_to_kill_stale_locks: @@ -249,7 +212,7 @@ class LockRoster: """ def __init__(self, path, id=None, kill_stale_locks=False): self.path = path - self.id = id or get_id() + self.id = id or get_process_id() self.ok_to_kill_zombie_locks = kill_stale_locks def load(self): @@ -264,7 +227,7 @@ class LockRoster: try: for e in data[key]: (host, pid, thread) = e - if not check_lock_stale(host, pid, thread): + if not process_alive(host, pid, thread): elements.add(tuple(e)) else: print(("Removed stale %s roster lock for pid %d." % (key, pid)), file=sys.stderr) @@ -330,7 +293,7 @@ class Lock: self.is_exclusive = exclusive self.sleep = sleep self.timeout = timeout - self.id = id or get_id() + self.id = id or get_process_id() # globally keeping track of shared and exclusive lockers: self._roster = LockRoster(path + '.roster', id=id, kill_stale_locks=kill_stale_locks) # an exclusive lock, used for: diff --git a/src/borg/platform/__init__.py b/src/borg/platform/__init__.py index e1772c5e1..5a37cf1b3 100644 --- a/src/borg/platform/__init__.py +++ b/src/borg/platform/__init__.py @@ -10,6 +10,7 @@ from .base import acl_get, acl_set from .base import set_flags, get_flags from .base import SaveFile, SyncFile, sync_dir, fdatasync from .base import swidth, API_VERSION +from .posix import process_alive, get_process_id, local_pid_alive if sys.platform.startswith('linux'): # pragma: linux only from .linux import acl_get, acl_set diff --git a/src/borg/platform/posix.pyx b/src/borg/platform/posix.pyx index c9726ea1a..1351254a7 100644 --- a/src/borg/platform/posix.pyx +++ b/src/borg/platform/posix.pyx @@ -1,3 +1,8 @@ + +import errno +import os +import socket + cdef extern from "wchar.h": cdef int wcswidth(const Py_UNICODE *str, size_t n) @@ -8,3 +13,53 @@ def swidth(s): return terminal_width else: return str_len + + +# only determine the PID and hostname once. +# for FUSE mounts, we fork a child process that needs to release +# the lock made by the parent, so it needs to use the same PID for that. +_pid = os.getpid() +# XXX this sometimes requires live internet access for issuing a DNS query in the background. +_hostname = socket.gethostname() + + +def get_process_id(): + """ + Return identification tuple (hostname, pid, thread_id) for 'us'. If this is a FUSE process, then the PID will be + that of the parent, not the forked FUSE child. + + Note: Currently thread_id is *always* zero. + """ + thread_id = 0 + return _hostname, _pid, thread_id + + +def process_alive(host, pid, thread): + """Check if the (host, pid, thread_id) combination corresponds to a dead process on our local node or not.""" + from . import local_pid_alive + + if host != _hostname: + return False + + if thread != 0: + # Currently thread is always 0, if we ever decide to set this to a non-zero value, + # this code needs to be revisited, too, to do a sensible thing + return False + + return local_pid_alive + +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) + # 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 + except OSError as err: + 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 + return True \ No newline at end of file