Move platform-dependent code to platform package

This commit is contained in:
Marian Beermann 2016-10-01 18:33:41 +02:00
parent d490292be3
commit c562f7750c
3 changed files with 62 additions and 43 deletions

View File

@ -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:

View File

@ -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

View File

@ -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