Jelajahi Sumber

Move platform-dependent code to platform package

Marian Beermann 8 tahun lalu
induk
melakukan
c562f7750c
3 mengubah file dengan 62 tambahan dan 43 penghapusan
  1. 6 43
      src/borg/locking.py
  2. 1 0
      src/borg/platform/__init__.py
  3. 55 0
      src/borg/platform/posix.pyx

+ 6 - 43
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:

+ 1 - 0
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

+ 55 - 0
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