re-format with black

This commit is contained in:
2021-05-03 17:24:04 -04:00
parent 4d7442d70c
commit 62ef10620f
12 changed files with 564 additions and 528 deletions
+2 -2
View File
@@ -37,11 +37,11 @@
""" """
from __future__ import (absolute_import, unicode_literals) from __future__ import absolute_import, unicode_literals
from .daemon import DaemonContext from .daemon import DaemonContext
# Local variables: # Local variables:
# coding: utf-8 # coding: utf-8
# mode: python # mode: python
+21 -18
View File
@@ -12,7 +12,7 @@
""" Package metadata for the python-daemon distribution. """ """ Package metadata for the python-daemon distribution. """
from __future__ import (absolute_import, unicode_literals) from __future__ import absolute_import, unicode_literals
import json import json
import re import re
@@ -21,10 +21,11 @@ import datetime
import pkg_resources import pkg_resources
distribution_name = "python-daemon" distribution_name = "python-daemon"
version_info_filename = "version_info.json" version_info_filename = "version_info.json"
def get_distribution_version_info(filename=version_info_filename): def get_distribution_version_info(filename=version_info_filename):
""" Get the version info from the installed distribution. """ Get the version info from the installed distribution.
@@ -37,10 +38,10 @@ def get_distribution_version_info(filename=version_info_filename):
""" """
version_info = { version_info = {
'release_date': "UNKNOWN", "release_date": "UNKNOWN",
'version': "UNKNOWN", "version": "UNKNOWN",
'maintainer': "UNKNOWN", "maintainer": "UNKNOWN",
} }
try: try:
distribution = pkg_resources.get_distribution(distribution_name) distribution = pkg_resources.get_distribution(distribution_name)
@@ -54,15 +55,16 @@ def get_distribution_version_info(filename=version_info_filename):
return version_info return version_info
version_info = get_distribution_version_info() version_info = get_distribution_version_info()
version_installed = version_info['version'] version_installed = version_info["version"]
rfc822_person_regex = re.compile(
"^(?P<name>[^<]+) <(?P<email>[^>]+)>$")
ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email']) rfc822_person_regex = re.compile("^(?P<name>[^<]+) <(?P<email>[^>]+)>$")
ParsedPerson = collections.namedtuple("ParsedPerson", ["name", "email"])
def parse_person_field(value): def parse_person_field(value):
""" Parse a person field into name and email address. """ Parse a person field into name and email address.
@@ -79,19 +81,18 @@ def parse_person_field(value):
match = rfc822_person_regex.match(value) match = rfc822_person_regex.match(value)
if len(value): if len(value):
if match is not None: if match is not None:
result = ParsedPerson( result = ParsedPerson(name=match.group("name"), email=match.group("email"))
name=match.group('name'),
email=match.group('email'))
else: else:
result = ParsedPerson(name=value, email=None) result = ParsedPerson(name=value, email=None)
return result return result
author_name = "Ben Finney" author_name = "Ben Finney"
author_email = "ben+python@benfinney.id.au" author_email = "ben+python@benfinney.id.au"
author = "{name} <{email}>".format(name=author_name, email=author_email) author = "{name} <{email}>".format(name=author_name, email=author_email)
class YearRange: class YearRange:
""" A range of years spanning a period. """ """ A range of years spanning a period. """
@@ -135,16 +136,18 @@ def make_year_range(begin_year, end_date=None):
return year_range return year_range
copyright_year_begin = "2001" copyright_year_begin = "2001"
build_date = version_info['release_date'] build_date = version_info["release_date"]
copyright_year_range = make_year_range(copyright_year_begin, build_date) copyright_year_range = make_year_range(copyright_year_begin, build_date)
copyright = "Copyright © {year_range} {author} and others".format( copyright = "Copyright © {year_range} {author} and others".format(
year_range=copyright_year_range, author=author) year_range=copyright_year_range, author=author
)
license = "Apache-2" license = "Apache-2"
url = "https://alioth.debian.org/projects/python-daemon/" url = "https://alioth.debian.org/projects/python-daemon/"
# Local variables: # Local variables:
# coding: utf-8 # coding: utf-8
# mode: python # mode: python
+68 -54
View File
@@ -18,7 +18,7 @@
""" Daemon process behaviour. """ Daemon process behaviour.
""" """
from __future__ import (absolute_import, unicode_literals) from __future__ import absolute_import, unicode_literals
import os import os
import sys import sys
@@ -27,6 +27,7 @@ import errno
import signal import signal
import socket import socket
import atexit import atexit
try: try:
# Python 2 has both str (bytes) and unicode (text). # Python 2 has both str (bytes) and unicode (text).
basestring = basestring basestring = basestring
@@ -36,7 +37,7 @@ except NameError:
basestring = str basestring = str
unicode = str unicode = str
class DaemonError(Exception): class DaemonError(Exception):
""" Base exception class for errors from this module. """ """ Base exception class for errors from this module. """
@@ -56,7 +57,7 @@ class DaemonOSEnvironmentError(DaemonError, OSError):
class DaemonProcessDetachError(DaemonError, OSError): class DaemonProcessDetachError(DaemonError, OSError):
""" Exception raised when process detach fails. """ """ Exception raised when process detach fails. """
class DaemonContext: class DaemonContext:
""" Context for turning the current program into a daemon process. """ Context for turning the current program into a daemon process.
@@ -231,21 +232,21 @@ class DaemonContext:
__metaclass__ = type __metaclass__ = type
def __init__( def __init__(
self, self,
chroot_directory=None, chroot_directory=None,
working_directory="/", working_directory="/",
umask=0, umask=0,
uid=None, uid=None,
gid=None, gid=None,
prevent_core=True, prevent_core=True,
detach_process=None, detach_process=None,
files_preserve=None, files_preserve=None,
pidfile=None, pidfile=None,
stdin=None, stdin=None,
stdout=None, stdout=None,
stderr=None, stderr=None,
signal_map=None, signal_map=None,
): ):
""" Set up a new instance. """ """ Set up a new instance. """
self.chroot_directory = chroot_directory self.chroot_directory = chroot_directory
self.working_directory = working_directory self.working_directory = working_directory
@@ -427,8 +428,10 @@ class DaemonContext:
""" """
exception = SystemExit( exception = SystemExit(
"Terminating on signal {signal_number!r}".format( "Terminating on signal {signal_number!r}".format(
signal_number=signal_number)) signal_number=signal_number
)
)
raise exception raise exception
def _get_exclude_file_descriptors(self): def _get_exclude_file_descriptors(self):
@@ -454,8 +457,10 @@ class DaemonContext:
if files_preserve is None: if files_preserve is None:
files_preserve = [] files_preserve = []
files_preserve.extend( files_preserve.extend(
item for item in [self.stdin, self.stdout, self.stderr] item
if hasattr(item, 'fileno')) for item in [self.stdin, self.stdout, self.stderr]
if hasattr(item, "fileno")
)
exclude_descriptors = set() exclude_descriptors = set()
for item in files_preserve: for item in files_preserve:
@@ -502,8 +507,9 @@ class DaemonContext:
""" """
signal_handler_map = dict( signal_handler_map = dict(
(signal_number, self._make_signal_handler(target)) (signal_number, self._make_signal_handler(target))
for (signal_number, target) in self.signal_map.items()) for (signal_number, target) in self.signal_map.items()
)
return signal_handler_map return signal_handler_map
@@ -520,7 +526,7 @@ def _get_file_descriptor(obj):
""" """
file_descriptor = None file_descriptor = None
if hasattr(obj, 'fileno'): if hasattr(obj, "fileno"):
try: try:
file_descriptor = obj.fileno() file_descriptor = obj.fileno()
except ValueError: except ValueError:
@@ -529,7 +535,7 @@ def _get_file_descriptor(obj):
return file_descriptor return file_descriptor
def change_working_directory(directory): def change_working_directory(directory):
""" Change the working directory of this process. """ Change the working directory of this process.
@@ -541,7 +547,8 @@ def change_working_directory(directory):
os.chdir(directory) os.chdir(directory)
except Exception as exc: except Exception as exc:
error = DaemonOSEnvironmentError( error = DaemonOSEnvironmentError(
"Unable to change working directory ({exc})".format(exc=exc)) "Unable to change working directory ({exc})".format(exc=exc)
)
raise error raise error
@@ -561,7 +568,8 @@ def change_root_directory(directory):
os.chroot(directory) os.chroot(directory)
except Exception as exc: except Exception as exc:
error = DaemonOSEnvironmentError( error = DaemonOSEnvironmentError(
"Unable to change root directory ({exc})".format(exc=exc)) "Unable to change root directory ({exc})".format(exc=exc)
)
raise error raise error
@@ -576,7 +584,8 @@ def change_file_creation_mask(mask):
os.umask(mask) os.umask(mask)
except Exception as exc: except Exception as exc:
error = DaemonOSEnvironmentError( error = DaemonOSEnvironmentError(
"Unable to change file creation mask ({exc})".format(exc=exc)) "Unable to change file creation mask ({exc})".format(exc=exc)
)
raise error raise error
@@ -597,10 +606,11 @@ def change_process_owner(uid, gid):
os.setuid(uid) os.setuid(uid)
except Exception as exc: except Exception as exc:
error = DaemonOSEnvironmentError( error = DaemonOSEnvironmentError(
"Unable to change process owner ({exc})".format(exc=exc)) "Unable to change process owner ({exc})".format(exc=exc)
)
raise error raise error
def prevent_core_dump(): def prevent_core_dump():
""" Prevent this process from generating a core dump. """ Prevent this process from generating a core dump.
@@ -618,15 +628,16 @@ def prevent_core_dump():
core_limit_prev = resource.getrlimit(core_resource) core_limit_prev = resource.getrlimit(core_resource)
except ValueError as exc: except ValueError as exc:
error = DaemonOSEnvironmentError( error = DaemonOSEnvironmentError(
"System does not support RLIMIT_CORE resource limit" "System does not support RLIMIT_CORE resource limit"
" ({exc})".format(exc=exc)) " ({exc})".format(exc=exc)
)
raise error raise error
# Set hard and soft limits to zero, i.e. no core dump at all. # Set hard and soft limits to zero, i.e. no core dump at all.
core_limit = (0, 0) core_limit = (0, 0)
resource.setrlimit(core_resource, core_limit) resource.setrlimit(core_resource, core_limit)
def detach_process_context(): def detach_process_context():
""" Detach the process context from parent and session. """ Detach the process context from parent and session.
@@ -656,15 +667,17 @@ def detach_process_context():
os._exit(0) os._exit(0)
except OSError as exc: except OSError as exc:
error = DaemonProcessDetachError( error = DaemonProcessDetachError(
"{message}: [{exc.errno:d}] {exc.strerror}".format( "{message}: [{exc.errno:d}] {exc.strerror}".format(
message=error_message, exc=exc)) message=error_message, exc=exc
)
)
raise error raise error
fork_then_exit_parent(error_message="Failed first fork") fork_then_exit_parent(error_message="Failed first fork")
os.setsid() os.setsid()
fork_then_exit_parent(error_message="Failed second fork") fork_then_exit_parent(error_message="Failed second fork")
def is_process_started_by_init(): def is_process_started_by_init():
""" Determine whether the current process is started by `init`. """ Determine whether the current process is started by `init`.
@@ -699,8 +712,7 @@ def is_socket(fd):
file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW) file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW)
try: try:
socket_type = file_socket.getsockopt( socket_type = file_socket.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE)
socket.SOL_SOCKET, socket.SO_TYPE)
except socket.error as exc: except socket.error as exc:
exc_errno = exc.args[0] exc_errno = exc.args[0]
if exc_errno == errno.ENOTSOCK: if exc_errno == errno.ENOTSOCK:
@@ -759,7 +771,7 @@ def is_detach_process_context_required():
return result return result
def close_file_descriptor_if_open(fd): def close_file_descriptor_if_open(fd):
""" Close a file descriptor if already open. """ Close a file descriptor if already open.
@@ -778,13 +790,14 @@ def close_file_descriptor_if_open(fd):
pass pass
else: else:
error = DaemonOSEnvironmentError( error = DaemonOSEnvironmentError(
"Failed to close file descriptor {fd:d} ({exc})".format( "Failed to close file descriptor {fd:d} ({exc})".format(fd=fd, exc=exc)
fd=fd, exc=exc)) )
raise error raise error
MAXFD = 2048 MAXFD = 2048
def get_maximum_file_descriptors(): def get_maximum_file_descriptors():
""" Get the maximum number of open file descriptors for this process. """ Get the maximum number of open file descriptors for this process.
@@ -820,7 +833,7 @@ def close_all_open_files(exclude=set()):
if fd not in exclude: if fd not in exclude:
close_file_descriptor_if_open(fd) close_file_descriptor_if_open(fd)
def redirect_stream(system_stream, target_stream): def redirect_stream(system_stream, target_stream):
""" Redirect a system stream to a specified file. """ Redirect a system stream to a specified file.
@@ -844,7 +857,7 @@ def redirect_stream(system_stream, target_stream):
target_fd = target_stream.fileno() target_fd = target_stream.fileno()
os.dup2(target_fd, system_stream.fileno()) os.dup2(target_fd, system_stream.fileno())
def make_default_signal_map(): def make_default_signal_map():
""" Make the default signal map for this system. """ Make the default signal map for this system.
@@ -855,15 +868,16 @@ def make_default_signal_map():
""" """
name_map = { name_map = {
'SIGTSTP': None, "SIGTSTP": None,
'SIGTTIN': None, "SIGTTIN": None,
'SIGTTOU': None, "SIGTTOU": None,
'SIGTERM': 'terminate', "SIGTERM": "terminate",
} }
signal_map = dict( signal_map = dict(
(getattr(signal, name), target) (getattr(signal, name), target)
for (name, target) in name_map.items() for (name, target) in name_map.items()
if hasattr(signal, name)) if hasattr(signal, name)
)
return signal_map return signal_map
@@ -895,7 +909,7 @@ def register_atexit_function(func):
""" """
atexit.register(func) atexit.register(func)
def _chain_exception_from_existing_exception_context(exc, as_cause=False): def _chain_exception_from_existing_exception_context(exc, as_cause=False):
""" Decorate the specified exception with the existing exception context. """ Decorate the specified exception with the existing exception context.
@@ -918,7 +932,7 @@ def _chain_exception_from_existing_exception_context(exc, as_cause=False):
exc.__context__ = existing_exc exc.__context__ = existing_exc
exc.__traceback__ = existing_traceback exc.__traceback__ = existing_traceback
# Local variables: # Local variables:
# coding: utf-8 # coding: utf-8
# mode: python # mode: python
+3 -3
View File
@@ -13,11 +13,11 @@
""" Lockfile behaviour implemented via Unix PID files. """ Lockfile behaviour implemented via Unix PID files.
""" """
from __future__ import (absolute_import, unicode_literals) from __future__ import absolute_import, unicode_literals
from lockfile.pidlockfile import PIDLockFile from lockfile.pidlockfile import PIDLockFile
class TimeoutPIDLockFile(PIDLockFile, object): class TimeoutPIDLockFile(PIDLockFile, object):
""" Lockfile with default timeout, implemented as a Unix PID file. """ Lockfile with default timeout, implemented as a Unix PID file.
@@ -59,7 +59,7 @@ class TimeoutPIDLockFile(PIDLockFile, object):
timeout = self.acquire_timeout timeout = self.acquire_timeout
super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs) super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
# Local variables: # Local variables:
# coding: utf-8 # coding: utf-8
# mode: python # mode: python
+26 -28
View File
@@ -17,12 +17,13 @@
""" Daemon runner library. """ Daemon runner library.
""" """
from __future__ import (absolute_import, unicode_literals) from __future__ import absolute_import, unicode_literals
import sys import sys
import os import os
import signal import signal
import errno import errno
try: try:
# Python 3 standard library. # Python 3 standard library.
ProcessLookupError ProcessLookupError
@@ -33,11 +34,11 @@ except NameError:
import lockfile import lockfile
from . import pidfile from . import pidfile
from .daemon import (basestring, unicode) from .daemon import basestring, unicode
from .daemon import DaemonContext from .daemon import DaemonContext
from .daemon import _chain_exception_from_existing_exception_context from .daemon import _chain_exception_from_existing_exception_context
class DaemonRunnerError(Exception): class DaemonRunnerError(Exception):
""" Abstract base class for errors from DaemonRunner. """ """ Abstract base class for errors from DaemonRunner. """
@@ -65,7 +66,7 @@ class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError):
class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError): class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError):
""" Raised when failure stopping DaemonRunner. """ """ Raised when failure stopping DaemonRunner. """
class DaemonRunner: class DaemonRunner:
""" Controller for a callable running in a separate background process. """ Controller for a callable running in a separate background process.
@@ -107,15 +108,13 @@ class DaemonRunner:
self.parse_args() self.parse_args()
self.app = app self.app = app
self.daemon_context = DaemonContext() self.daemon_context = DaemonContext()
self.daemon_context.stdin = open(app.stdin_path, 'rt') self.daemon_context.stdin = open(app.stdin_path, "rt")
self.daemon_context.stdout = open(app.stdout_path, 'w+t') self.daemon_context.stdout = open(app.stdout_path, "w+t")
self.daemon_context.stderr = open( self.daemon_context.stderr = open(app.stderr_path, "w+t", buffering=0)
app.stderr_path, 'w+t', buffering=0)
self.pidfile = None self.pidfile = None
if app.pidfile_path is not None: if app.pidfile_path is not None:
self.pidfile = make_pidlockfile( self.pidfile = make_pidlockfile(app.pidfile_path, app.pidfile_timeout)
app.pidfile_path, app.pidfile_timeout)
self.daemon_context.pidfile = self.pidfile self.daemon_context.pidfile = self.pidfile
def _usage_exit(self, argv): def _usage_exit(self, argv):
@@ -130,7 +129,8 @@ class DaemonRunner:
usage_exit_code = 2 usage_exit_code = 2
action_usage = "|".join(self.action_funcs.keys()) action_usage = "|".join(self.action_funcs.keys())
message = "usage: {progname} {usage}".format( message = "usage: {progname} {usage}".format(
progname=progname, usage=action_usage) progname=progname, usage=action_usage
)
emit_message(message) emit_message(message)
sys.exit(usage_exit_code) sys.exit(usage_exit_code)
@@ -175,8 +175,8 @@ class DaemonRunner:
self.daemon_context.open() self.daemon_context.open()
except lockfile.AlreadyLocked: except lockfile.AlreadyLocked:
error = DaemonRunnerStartFailureError( error = DaemonRunnerStartFailureError(
"PID file {pidfile.path!r} already locked".format( "PID file {pidfile.path!r} already locked".format(pidfile=self.pidfile)
pidfile=self.pidfile)) )
raise error raise error
pid = os.getpid() pid = os.getpid()
@@ -198,8 +198,8 @@ class DaemonRunner:
os.kill(pid, signal.SIGTERM) os.kill(pid, signal.SIGTERM)
except OSError as exc: except OSError as exc:
error = DaemonRunnerStopFailureError( error = DaemonRunnerStopFailureError(
"Failed to terminate {pid:d}: {exc}".format( "Failed to terminate {pid:d}: {exc}".format(pid=pid, exc=exc)
pid=pid, exc=exc)) )
raise error raise error
def _stop(self): def _stop(self):
@@ -212,8 +212,8 @@ class DaemonRunner:
""" """
if not self.pidfile.is_locked(): if not self.pidfile.is_locked():
error = DaemonRunnerStopFailureError( error = DaemonRunnerStopFailureError(
"PID file {pidfile.path!r} not locked".format( "PID file {pidfile.path!r} not locked".format(pidfile=self.pidfile)
pidfile=self.pidfile)) )
raise error raise error
if is_pidfile_stale(self.pidfile): if is_pidfile_stale(self.pidfile):
@@ -228,10 +228,10 @@ class DaemonRunner:
self._start() self._start()
action_funcs = { action_funcs = {
'start': _start, "start": _start,
'stop': _stop, "stop": _stop,
'restart': _restart, "restart": _restart,
} }
def _get_action_func(self): def _get_action_func(self):
""" Get the function for the specified action. """ Get the function for the specified action.
@@ -249,8 +249,8 @@ class DaemonRunner:
func = self.action_funcs[self.action] func = self.action_funcs[self.action]
except KeyError: except KeyError:
error = DaemonRunnerInvalidActionError( error = DaemonRunnerInvalidActionError(
"Unknown action: {action!r}".format( "Unknown action: {action!r}".format(action=self.action)
action=self.action)) )
raise error raise error
return func return func
@@ -278,12 +278,10 @@ def emit_message(message, stream=None):
def make_pidlockfile(path, acquire_timeout): def make_pidlockfile(path, acquire_timeout):
""" Make a PIDLockFile instance with the given filesystem path. """ """ Make a PIDLockFile instance with the given filesystem path. """
if not isinstance(path, basestring): if not isinstance(path, basestring):
error = ValueError("Not a filesystem path: {path!r}".format( error = ValueError("Not a filesystem path: {path!r}".format(path=path))
path=path))
raise error raise error
if not os.path.isabs(path): if not os.path.isabs(path):
error = ValueError("Not an absolute path: {path!r}".format( error = ValueError("Not an absolute path: {path!r}".format(path=path))
path=path))
raise error raise error
lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout) lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout)
@@ -316,7 +314,7 @@ def is_pidfile_stale(pidfile):
return result return result
# Local variables: # Local variables:
# coding: utf-8 # coding: utf-8
# mode: python # mode: python
+298 -308
View File
@@ -1,4 +1,3 @@
""" """
host and connection class shared between hbd and host and connection class shared between hbd and
the websit's heartbeat.py the websit's heartbeat.py
@@ -13,358 +12,349 @@ num = 0
MAXRTTS = 10 MAXRTTS = 10
DEBUG=2 DEBUG = 2
def log(host, m): def log(host, m):
if DEBUG: if DEBUG:
print("class log: %s %s" % (host, m)) print("class log: %s %s" % (host, m))
class Connection: class Connection:
# map of addrs to names # map of addrs to names
htab = {} htab = {}
unknown = "unknown" unknown = "unknown"
up = "up" up = "up"
down = "down" down = "down"
overdue = "overdue" overdue = "overdue"
def __init__(self, host, cid, addr, afam): def __init__(self, host, cid, addr, afam):
self.host = host self.host = host
self.cid = cid self.cid = cid
self.addr = addr self.addr = addr
self.afam = afam self.afam = afam
self.rtts = [0] self.rtts = [0]
self.lastbeat = time.time() self.lastbeat = time.time()
self.statetime = self.lastbeat self.statetime = self.lastbeat
self.deltastatetime = 'computed' self.deltastatetime = "computed"
self.state = Connection.unknown self.state = Connection.unknown
if host: if host:
r = "new addr %s" % (addr) r = "new addr %s" % (addr)
Connection.htab[addr] = self.host.name Connection.htab[addr] = self.host.name
if self.host.isDynDns(): if self.host.isDynDns():
log(self.host.name, "dns update %s" % self.addr) log(self.host.name, "dns update %s" % self.addr)
Host.dnsQ.put((self.host.name, self.addr)) Host.dnsQ.put((self.host.name, self.addr))
def registerDns(self): def registerDns(self):
Host.dnsQ.put((self.host.name, self.addr)) Host.dnsQ.put((self.host.name, self.addr))
def statedict(self, Null=False):
d = {}
now = time.time()
if not Null:
d["addr"] = self.addr
if self.rtts[-1]:
d["rtt"] = "%0.1f" % self.rtts[-1]
elif self.state == Connection.unknown:
d["rtt"] = ""
else:
d["rtt"] = "?"
d["lastbeat"] = self.lastbeat
if self.state == Connection.overdue:
d["state"] = "<b>%s</b>" % self.state
else:
d["state"] = self.state
if self.state == Connection.up:
d["rttstate"] = d["rtt"]
elif self.state == Connection.overdue:
d["rttstate"] = ""
else:
d["rttstate"] = d["state"]
d["statetime"] = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(self.statetime)
)
delta = now - self.statetime
def statedict(self, Null=False): if self.state == Connection.unknown:
d = {} d["deltastatetime"] = ""
now = time.time() elif delta > 86400:
if not Null: # d['deltastatetime'] = time.strftime("%d %H:%M:%S", time.gmtime(delta))
d['addr'] = self.addr d["deltastatetime"] = "%0.1f days" % (delta / 86400.0)
if self.rtts[-1]: elif delta > 3600:
d['rtt'] = "%0.1f" % self.rtts[-1] # d['deltastatetime'] = time.strftime("%H:%M:%S", time.gmtime(delta))
elif self.state == Connection.unknown: d["deltastatetime"] = time.strftime("%k:%M hrs", time.gmtime(delta))
d['rtt'] = "" # d['deltastatetime'] = "%0.1f hrs" % (delta / 3600.)
else: elif delta > 60:
d['rtt'] = "?" # d['deltastatetime'] = time.strftime("%M:%S", time.gmtime(delta))
d['lastbeat'] = self.lastbeat d["deltastatetime"] = time.strftime("%M:%S mins", time.gmtime(delta))
if self.state == Connection.overdue: # d['deltastatetime'] = "%0.1f mins" % (delta / 60.)
d['state'] = "<b>%s</b>" % self.state else:
else: # d['deltastatetime'] = time.strftime("%S", time.gmtime(delta))
d['state'] = self.state d["deltastatetime"] = "%i secs" % (delta)
if self.state == Connection.up:
d['rttstate'] = d['rtt']
elif self.state == Connection.overdue:
d['rttstate'] = ''
else:
d['rttstate'] = d['state']
d['statetime'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.statetime))
delta = now - self.statetime
if self.state == Connection.unknown: else:
d['deltastatetime'] = '' d["addr"] = ""
elif delta > 86400: d["rtt"] = ""
# d['deltastatetime'] = time.strftime("%d %H:%M:%S", time.gmtime(delta)) d["lastbeat"] = ""
d['deltastatetime'] = "%0.1f days" % (delta / 86400.) d["state"] = ""
elif delta > 3600: d["statetime"] = ""
# d['deltastatetime'] = time.strftime("%H:%M:%S", time.gmtime(delta)) d["deltastatetime"] = ""
d['deltastatetime'] = time.strftime("%k:%M hrs", time.gmtime(delta)) d["rttstate"] = ""
# d['deltastatetime'] = "%0.1f hrs" % (delta / 3600.) return d
elif delta > 60:
# d['deltastatetime'] = time.strftime("%M:%S", time.gmtime(delta))
d['deltastatetime'] = time.strftime("%M:%S mins", time.gmtime(delta))
# d['deltastatetime'] = "%0.1f mins" % (delta / 60.)
else:
# d['deltastatetime'] = time.strftime("%S", time.gmtime(delta))
d['deltastatetime'] = "%i secs" % (delta)
else: def headerdict(self, afam):
d['addr'] = '' d = {}
d['rtt'] = "" d["addr"] = "%s Addr" % afam
d['lastbeat'] = '' d["rtt"] = "Latencey"
d['state'] = '' d["lastbeat"] = "Last Contact"
d['statetime'] = '' d["state"] = "State"
d['deltastatetime'] = '' d["statetime"] = "Last State"
d['rttstate'] = '' d["rttstate"] = "Reach"
return d d["deltastatetime"] = "Last State"
return d
def jsons(self):
return json.dumps(self.__dict__)
def headerdict(self, afam): # set new state, return number of secs in previous state
d = {} def newstate(self, state, now, when=0):
d['addr'] = '%s Addr' % afam self.state = state
d['rtt'] = 'Latencey' delta = now - when
d['lastbeat'] = 'Last Contact' s = delta - self.statetime
d['state'] = 'State' self.statetime = delta
d['statetime'] = 'Last State' return s
d['rttstate'] = 'Reach'
d['deltastatetime'] = 'Last State'
return d
def getstate(self):
return self.state
def jsons(self): def newaddr(self, addr, rtt, now):
return(json.dumps(self.__dict__)) self.lastbeat = now
self.rtts.append(rtt)
if len(self.rtts) > MAXRTTS:
del self.rtts[0]
if self.addr == addr:
# set new state, return number of secs in previous state r = None
def newstate(self, state, now, when=0): else:
self.state = state r = "changed from %s to %s" % (self.addr, addr)
delta = now - when try:
s = delta - self.statetime del Connection.htab[self.addr]
self.statetime = delta except:
return s pass
self.addr = addr
Connection.htab[addr] = self.host.name
def getstate(self): if self.host.isDynDns():
return self.state Host.dnsQ.put((self.host.name, self.addr))
return r
def newaddr(self, addr, rtt, now):
self.lastbeat = now
self.rtts.append(rtt)
if len(self.rtts) > MAXRTTS:
del self.rtts[0]
if self.addr == addr:
r = None
else:
r = "changed from %s to %s" % (self.addr, addr)
try:
del Connection.htab[self.addr]
except:
pass
self.addr = addr
Connection.htab[addr] = self.host.name
if self.host.isDynDns():
Host.dnsQ.put((self.host.name, self.addr))
return r
# #
class Host: class Host:
# Table of Hosts # Table of Hosts
hosts = {} hosts = {}
def __init__(self, name): def __init__(self, name):
global num global num
self.name = name self.name = name
if name: if name:
num += 1 num += 1
Host.hosts[name] = self Host.hosts[name] = self
self.num = num self.num = num
self.dyn = False self.dyn = False
self.watched = False self.watched = False
self.upcount = 0 self.upcount = 0
self.interval = 0 self.interval = 0
self.doesack = -1 self.doesack = -1
self.cmds = [] self.cmds = []
self.cver = 0 self.cver = 0
self.connections = {} self.connections = {}
self.hdwcounts = [[0,0],[0,0],[0,0]] self.hdwcounts = [[0, 0], [0, 0], [0, 0]]
def statedict(self):
d = {}
d["name"] = self.name
if self.dyn:
d["name"] += "*"
if self.watched:
d["name"] = "<b>%s</b>" % d["name"]
d["dyn"] = str(self.dyn)
d["ver"] = str(self.cver)
d["num"] = self.num
for c in ["IPv4", "IPv6"]:
if c in self.connections:
cs = self.connections[c].statedict()
else:
cs = ubConnection.statedict(True)
for csv in cs:
d["%s.%s" % (c, csv)] = cs[csv]
def statedict(self): return d
d = {}
d['name'] = self.name
if self.dyn:
d['name'] += "*"
if self.watched:
d['name'] = "<b>%s</b>" % d['name']
d['dyn'] = str(self.dyn)
d['ver'] = str(self.cver)
d['num'] = self.num
for c in ['IPv4', 'IPv6']:
if c in self.connections:
cs = self.connections[c].statedict()
else:
cs = ubConnection.statedict(True)
for csv in cs:
d['%s.%s' % (c, csv) ] = cs[csv]
return d def headerdict(self):
d = {}
d["name"] = "Name"
d["dyn"] = "Dyn"
d["ver"] = "Ver"
d["num"] = "??"
for c in ["IPv4", "IPv6"]:
cs = ubConnection.headerdict(c)
for csv in cs:
d["%s.%s" % (c, csv)] = cs[csv]
return d
def registerDns(self):
for af in self.connections:
self.connections[af].registerDns()
def headerdict(self): def jsons(self):
d = {} ddict = {}
d['name'] = 'Name' for d in self.__dict__:
d['dyn'] = 'Dyn' if d == "connections":
d['ver'] = 'Ver' cl = []
d['num'] = '??' for c in self.connections:
for c in ['IPv4', 'IPv6']: # dirty ugly hack: fix conn to host backpointer
cs = ubConnection.headerdict(c) cld = copy.deepcopy(self.connections[c].__dict__)
for csv in cs: cld["host"] = cld["host"].name
d['%s.%s' % (c, csv) ] = cs[csv] cl.append(cld)
return d ddict[d] = cl
else:
ddict[d] = self.__dict__[d]
return json.dumps(ddict)
def setcver(self, cver):
self.cver = cver
def registerDns(self): def isDynDns(self):
for af in self.connections: return self.dyn
self.connections[af].registerDns()
def isIPv4(self, addr):
if type(addr) == type(()):
return addr[0].find(".") > 0
else:
return addr.find(".") > 0
def jsons(self): def conndata(self, cid, addr, rtt, now):
ddict = {} if self.isIPv4(addr):
for d in self.__dict__: afam = "IPv4"
if d == 'connections': else:
cl = [] afam = "IPv6"
for c in self.connections:
# dirty ugly hack: fix conn to host backpointer
cld = copy.deepcopy(self.connections[c].__dict__)
cld['host'] = cld['host'].name
cl.append(cld)
ddict[d] = cl
else:
ddict[d] = self.__dict__[d]
return json.dumps(ddict)
if afam not in self.connections:
self.connections[afam] = Connection(self, cid, addr, afam)
def setcver(self, cver): conn = self.connections[afam]
self.cver = cver res = conn.newaddr(addr, rtt, now)
return conn, res
# called when reloading class from pickle, add new fields here
def fixup(self):
pass
def isDynDns(self): def dispstate(self):
return self.dyn if self.state in ["down", "overdue"]:
state = "<b>%s</b>" % self.state
elif self.state in ["up", "UP"]:
state = ""
for x in list(self.connections.keys()):
try:
state += " %5.1f" % (self.connections[x].rtts[-1])
except:
state += " %5s" % (self.connections[x].rtts[-1])
elif self.state in ["unknown", "UNKNOWN"]:
state = ""
else:
state = "%s" % self.state
return state
def dispstats(self):
if self.doesack != -1:
if self.upcount > 0:
# return "(%0.1f%%) %s %s %s " % ((self.doesack * 100.0) / self.upcount, self.doesack, self.upcount, self.hdwcounts)
r = ""
for v in range(3):
a, u = self.hdwcounts[v]
if (self.upcount - u) != 0:
vs = "%0.0f" % (
100.0 - (((self.doesack - a) * 100.0) / (self.upcount - u))
)
if vs == "0":
vs = ""
else:
vs = "-"
r += '<td align="right">%s</td>' % vs
return r
else:
return "<td>(%s)</td><td></td><td></td>" % (self.doesack)
return '<td align="right">N/A</td><td></td<td></td>>'
def isIPv4(self, addr): hostfields_long = [
if type(addr) == type(()): "name",
return addr[0].find('.') > 0 "IPv4.addr",
else: "IPv4.state",
return addr.find('.') > 0 ("IPv4.rtt", 'style="text-align: right;"'),
("IPv4.statetime", 'style="text-align: right;"'),
"IPv6.addr",
"IPv6.state",
("IPv6.rtt", 'style="text-align: right;"'),
("IPv6.statetime", 'style="text-align: right;"'),
"ver",
]
hostfields_short = [
"name",
("IPv4.rttstate", 'style="text-align: right;"'),
("IPv4.deltastatetime", 'style="text-align: right;"'),
("IPv6.rttstate", 'style="text-align: right;"'),
("IPv6.deltastatetime", 'style="text-align: right;"'),
]
def conndata(self, cid, addr, rtt, now): def gene(self, tag, v, attrib=None):
if self.isIPv4(addr): if attrib:
afam = "IPv4" a = " %s" % attrib
else: else:
afam = "IPv6" a = ""
return "<%s%s>%s</%s>" % (tag, a, v, tag)
if afam not in self.connections: def htmltable(self, tag, hd, short):
self.connections[afam] = Connection(self, cid, addr, afam) if short:
hostfields = Host.hostfields_short
else:
hostfields = Host.hostfields_long
h = []
for f in hostfields:
if type(f) == type(()):
h.append(self.gene(tag, hd[f[0]], f[1]))
else:
h.append(self.gene(tag, hd[f]))
return self.gene("tr", "\n".join(h))
conn = self.connections[afam] def buildhosttable(self, short=False):
res = conn.newaddr(addr, rtt, now) if DEBUG > 1:
return conn, res print("DBG buildhosttable: start")
res = []
res.append('<table id="ntable" class="sortable">')
# called when reloading class from pickle, add new fields here res.append(ubHost.htmltable("th", ubHost.headerdict(), short))
def fixup(self): hosts_sorted = list(Host.hosts.keys())
pass if len(hosts_sorted):
hosts_sorted.sort()
for h in hosts_sorted:
def dispstate(self): res.append(ubHost.htmltable("td", Host.hosts[h].statedict(), short))
if self.state in ["down", "overdue"]: res.append("</table>")
state = "<b>%s</b>" % self.state if DEBUG > 1:
elif self.state in ["up", "UP"]: print("DBG buildhosttable: %s" % res)
state = "" return res
for x in list(self.connections.keys()):
try:
state += " %5.1f" % (self.connections[x].rtts[-1])
except:
state += " %5s" % (self.connections[x].rtts[-1])
elif self.state in ["unknown", "UNKNOWN"]:
state = ""
else:
state = "%s" % self.state
return state
def dispstats(self):
if self.doesack != -1:
if self.upcount > 0:
# return "(%0.1f%%) %s %s %s " % ((self.doesack * 100.0) / self.upcount, self.doesack, self.upcount, self.hdwcounts)
r = ""
for v in range(3):
a,u = self.hdwcounts[v]
if (self.upcount - u) != 0:
vs = "%0.0f" % (100.0 - (((self.doesack - a) * 100.0) / (self.upcount - u)))
if vs == "0":
vs=""
else:
vs="-"
r+= '<td align="right">%s</td>' % vs
return r
else:
return "<td>(%s)</td><td></td><td></td>" % (self.doesack)
return '<td align="right">N/A</td><td></td<td></td>>'
hostfields_long = ['name', 'IPv4.addr',
'IPv4.state', ('IPv4.rtt','style="text-align: right;"'),
('IPv4.statetime','style="text-align: right;"'), 'IPv6.addr',
'IPv6.state', ('IPv6.rtt', 'style="text-align: right;"'),
('IPv6.statetime','style="text-align: right;"'), 'ver']
hostfields_short = ['name',
('IPv4.rttstate','style="text-align: right;"'),
('IPv4.deltastatetime','style="text-align: right;"'),
('IPv6.rttstate','style="text-align: right;"'),
('IPv6.deltastatetime','style="text-align: right;"')]
def gene(self, tag, v, attrib=None):
if attrib:
a=" %s" % attrib
else:
a=""
return "<%s%s>%s</%s>" % (tag, a, v, tag)
def htmltable(self, tag, hd, short):
if short:
hostfields = Host.hostfields_short
else:
hostfields = Host.hostfields_long
h = []
for f in hostfields:
if type(f) == type(()):
h.append(self.gene(tag, hd[f[0]], f[1]))
else:
h.append(self.gene(tag, hd[f]))
return self.gene("tr", "\n".join(h))
def buildhosttable(self, short=False):
if DEBUG > 1:
print("DBG buildhosttable: start")
res = []
res.append('<table id="ntable" class="sortable">')
res.append(ubHost.htmltable('th', ubHost.headerdict(), short))
hosts_sorted = list(Host.hosts.keys())
if len(hosts_sorted):
hosts_sorted.sort()
for h in hosts_sorted:
res.append(ubHost.htmltable('td', Host.hosts[h].statedict(), short))
res.append("</table>")
if DEBUG > 1:
print("DBG buildhosttable: %s" % res)
return res
def buildmsgtable(self, msgs):
res = []
le = max(40 - len(Host.hosts), 3)
res.append("<h4>Log of Events</h4>")
for m in msgs[len(msgs)-le:]:
res.append("%s<BR>" % m)
return res
def buildmsgtable(self, msgs):
res = []
le = max(40 - len(Host.hosts), 3)
res.append("<h4>Log of Events</h4>")
for m in msgs[len(msgs) - le :]:
res.append("%s<BR>" % m)
return res
# create fake "unbound objects", remove in Python 3.0 # create fake "unbound objects", remove in Python 3.0
ubHost = Host(None) ubHost = Host(None)
ubConnection = Connection(None, "", "", "") ubConnection = Connection(None, "", "", "")
+50 -20
View File
@@ -65,10 +65,21 @@ if not hasattr(threading, "current_thread"):
if not hasattr(threading.Thread, "get_name"): if not hasattr(threading.Thread, "get_name"):
threading.Thread.get_name = threading.Thread.getName threading.Thread.get_name = threading.Thread.getName
__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked', __all__ = [
'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock', "Error",
'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock', "LockError",
'LockBase', 'locked'] "LockTimeout",
"AlreadyLocked",
"LockFailed",
"UnlockError",
"NotLocked",
"NotMyLock",
"LinkFileLock",
"MkdirFileLock",
"SQLiteFileLock",
"LockBase",
"locked",
]
class Error(Exception): class Error(Exception):
@@ -80,6 +91,7 @@ class Error(Exception):
... except Exception: ... except Exception:
... pass ... pass
""" """
pass pass
@@ -92,6 +104,7 @@ class LockError(Error):
... except Error: ... except Error:
... pass ... pass
""" """
pass pass
@@ -103,6 +116,7 @@ class LockTimeout(LockError):
... except LockError: ... except LockError:
... pass ... pass
""" """
pass pass
@@ -114,6 +128,7 @@ class AlreadyLocked(LockError):
... except LockError: ... except LockError:
... pass ... pass
""" """
pass pass
@@ -125,6 +140,7 @@ class LockFailed(LockError):
... except LockError: ... except LockError:
... pass ... pass
""" """
pass pass
@@ -137,6 +153,7 @@ class UnlockError(Error):
... except Error: ... except Error:
... pass ... pass
""" """
pass pass
@@ -148,6 +165,7 @@ class NotLocked(UnlockError):
... except UnlockError: ... except UnlockError:
... pass ... pass
""" """
pass pass
@@ -159,6 +177,7 @@ class NotMyLock(UnlockError):
... except UnlockError: ... except UnlockError:
... pass ... pass
""" """
pass pass
@@ -209,6 +228,7 @@ class _SharedBase(object):
class LockBase(_SharedBase): class LockBase(_SharedBase):
"""Base class for platform-specific lock classes.""" """Base class for platform-specific lock classes."""
def __init__(self, path, threaded=True, timeout=None): def __init__(self, path, threaded=True, timeout=None):
""" """
>>> lock = LockBase('somefile') >>> lock = LockBase('somefile')
@@ -223,7 +243,7 @@ class LockBase(_SharedBase):
# Thread objects in Python 2.4 and earlier do not have ident # Thread objects in Python 2.4 and earlier do not have ident
# attrs. Worm around that. # attrs. Worm around that.
ident = getattr(t, "ident", hash(t)) ident = getattr(t, "ident", hash(t))
self.tname = "-%x" % (ident & 0xffffffff) self.tname = "-%x" % (ident & 0xFFFFFFFF)
else: else:
self.tname = "" self.tname = ""
dirname = os.path.dirname(self.lock_file) dirname = os.path.dirname(self.lock_file)
@@ -235,11 +255,10 @@ class LockBase(_SharedBase):
# and overwriting the already existing lock-file, then one # and overwriting the already existing lock-file, then one
# gets unlocked, deleting both lock-file and unique file, # gets unlocked, deleting both lock-file and unique file,
# finally the last lock errors out upon releasing. # finally the last lock errors out upon releasing.
self.unique_name = os.path.join(dirname, self.unique_name = os.path.join(
"%s%s.%s%s" % (self.hostname, dirname,
self.tname, "%s%s.%s%s" % (self.hostname, self.tname, self.pid, hash(self.path)),
self.pid, )
hash(self.path)))
self.timeout = timeout self.timeout = timeout
def is_locked(self): def is_locked(self):
@@ -261,13 +280,15 @@ class LockBase(_SharedBase):
raise NotImplemented("implement in subclass") raise NotImplemented("implement in subclass")
def __repr__(self): def __repr__(self):
return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name, return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name, self.path)
self.path)
def _fl_helper(cls, mod, *args, **kwds): def _fl_helper(cls, mod, *args, **kwds):
warnings.warn("Import from %s module instead of lockfile package" % mod, warnings.warn(
DeprecationWarning, stacklevel=2) "Import from %s module instead of lockfile package" % mod,
DeprecationWarning,
stacklevel=2,
)
# This is a bit funky, but it's only for awhile. The way the unit tests # This is a bit funky, but it's only for awhile. The way the unit tests
# are constructed this function winds up as an unbound method, so it # are constructed this function winds up as an unbound method, so it
# actually takes three args, not two. We want to toss out self. # actually takes three args, not two. We want to toss out self.
@@ -286,8 +307,8 @@ def LinkFileLock(*args, **kwds):
lockfile.linklockfile module. lockfile.linklockfile module.
""" """
from . import linklockfile from . import linklockfile
return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
*args, **kwds) return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile", *args, **kwds)
def MkdirFileLock(*args, **kwds): def MkdirFileLock(*args, **kwds):
@@ -297,8 +318,10 @@ def MkdirFileLock(*args, **kwds):
lockfile.mkdirlockfile module. lockfile.mkdirlockfile module.
""" """
from . import mkdirlockfile from . import mkdirlockfile
return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
*args, **kwds) return _fl_helper(
mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile", *args, **kwds
)
def SQLiteFileLock(*args, **kwds): def SQLiteFileLock(*args, **kwds):
@@ -308,8 +331,10 @@ def SQLiteFileLock(*args, **kwds):
lockfile.mkdirlockfile module. lockfile.mkdirlockfile module.
""" """
from . import sqlitelockfile from . import sqlitelockfile
return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
*args, **kwds) return _fl_helper(
sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile", *args, **kwds
)
def locked(path, timeout=None): def locked(path, timeout=None):
@@ -324,6 +349,7 @@ def locked(path, timeout=None):
def myname(...): def myname(...):
... ...
""" """
def decor(func): def decor(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
@@ -333,15 +359,19 @@ def locked(path, timeout=None):
return func(*args, **kwargs) return func(*args, **kwargs)
finally: finally:
lock.release() lock.release()
return wrapper return wrapper
return decor return decor
if hasattr(os, "link"): if hasattr(os, "link"):
from . import linklockfile as _llf from . import linklockfile as _llf
LockFile = _llf.LinkLockFile LockFile = _llf.LinkLockFile
else: else:
from . import mkdirlockfile as _mlf from . import mkdirlockfile as _mlf
LockFile = _mlf.MkdirLockFile LockFile = _mlf.MkdirLockFile
FileLock = LockFile FileLock = LockFile
+10 -10
View File
@@ -3,8 +3,7 @@ from __future__ import absolute_import
import time import time
import os import os
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, from . import LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
AlreadyLocked)
class LinkLockFile(LockBase): class LinkLockFile(LockBase):
@@ -41,12 +40,11 @@ class LinkLockFile(LockBase):
if timeout is not None and time.time() > end_time: if timeout is not None and time.time() > end_time:
os.unlink(self.unique_name) os.unlink(self.unique_name)
if timeout > 0: if timeout > 0:
raise LockTimeout("Timeout waiting to acquire" raise LockTimeout(
" lock for %s" % "Timeout waiting to acquire" " lock for %s" % self.path
self.path) )
else: else:
raise AlreadyLocked("%s is already locked" % raise AlreadyLocked("%s is already locked" % self.path)
self.path)
time.sleep(timeout is not None and timeout / 10 or 0.1) time.sleep(timeout is not None and timeout / 10 or 0.1)
else: else:
# Link creation succeeded. We're good to go. # Link creation succeeded. We're good to go.
@@ -64,9 +62,11 @@ class LinkLockFile(LockBase):
return os.path.exists(self.lock_file) return os.path.exists(self.lock_file)
def i_am_locking(self): def i_am_locking(self):
return (self.is_locked() and return (
os.path.exists(self.unique_name) and self.is_locked()
os.stat(self.unique_name).st_nlink == 2) and os.path.exists(self.unique_name)
and os.stat(self.unique_name).st_nlink == 2
)
def break_lock(self): def break_lock(self):
if os.path.exists(self.lock_file): if os.path.exists(self.lock_file):
+10 -13
View File
@@ -5,12 +5,12 @@ import os
import sys import sys
import errno import errno
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, from . import LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
AlreadyLocked)
class MkdirLockFile(LockBase): class MkdirLockFile(LockBase):
"""Lock file by creating a directory.""" """Lock file by creating a directory."""
def __init__(self, path, threaded=True, timeout=None): def __init__(self, path, threaded=True, timeout=None):
""" """
>>> lock = MkdirLockFile('somefile') >>> lock = MkdirLockFile('somefile')
@@ -19,10 +19,9 @@ class MkdirLockFile(LockBase):
LockBase.__init__(self, path, threaded, timeout) LockBase.__init__(self, path, threaded, timeout)
# Lock file itself is a directory. Place the unique file name into # Lock file itself is a directory. Place the unique file name into
# it. # it.
self.unique_name = os.path.join(self.lock_file, self.unique_name = os.path.join(
"%s.%s%s" % (self.hostname, self.lock_file, "%s.%s%s" % (self.hostname, self.tname, self.pid)
self.tname, )
self.pid))
def acquire(self, timeout=None): def acquire(self, timeout=None):
timeout = timeout if timeout is not None else self.timeout timeout = timeout if timeout is not None else self.timeout
@@ -47,13 +46,12 @@ class MkdirLockFile(LockBase):
return return
if timeout is not None and time.time() > end_time: if timeout is not None and time.time() > end_time:
if timeout > 0: if timeout > 0:
raise LockTimeout("Timeout waiting to acquire" raise LockTimeout(
" lock for %s" % "Timeout waiting to acquire" " lock for %s" % self.path
self.path) )
else: else:
# Someone else has the lock. # Someone else has the lock.
raise AlreadyLocked("%s is already locked" % raise AlreadyLocked("%s is already locked" % self.path)
self.path)
time.sleep(wait) time.sleep(wait)
else: else:
# Couldn't create the lock for some other reason # Couldn't create the lock for some other reason
@@ -74,8 +72,7 @@ class MkdirLockFile(LockBase):
return os.path.exists(self.lock_file) return os.path.exists(self.lock_file)
def i_am_locking(self): def i_am_locking(self):
return (self.is_locked() and return self.is_locked() and os.path.exists(self.unique_name)
os.path.exists(self.unique_name))
def break_lock(self): def break_lock(self):
if os.path.exists(self.lock_file): if os.path.exists(self.lock_file):
+9 -11
View File
@@ -18,10 +18,9 @@ import errno
import os import os
import time import time
from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock, from . import LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock, LockTimeout
LockTimeout)
class PIDLockFile(LockBase): class PIDLockFile(LockBase):
""" Lockfile implemented as a Unix PID file. """ Lockfile implemented as a Unix PID file.
@@ -80,12 +79,11 @@ class PIDLockFile(LockBase):
# The lock creation failed. Maybe sleep a bit. # The lock creation failed. Maybe sleep a bit.
if time.time() > end_time: if time.time() > end_time:
if timeout is not None and timeout > 0: if timeout is not None and timeout > 0:
raise LockTimeout("Timeout waiting to acquire" raise LockTimeout(
" lock for %s" % "Timeout waiting to acquire" " lock for %s" % self.path
self.path) )
else: else:
raise AlreadyLocked("%s is already locked" % raise AlreadyLocked("%s is already locked" % self.path)
self.path)
time.sleep(timeout is not None and timeout / 10 or 0.1) time.sleep(timeout is not None and timeout / 10 or 0.1)
else: else:
raise LockFailed("failed to create %s" % self.path) raise LockFailed("failed to create %s" % self.path)
@@ -125,7 +123,7 @@ def read_pid_from_pidfile(pidfile_path):
""" """
pid = None pid = None
try: try:
pidfile = open(pidfile_path, 'r') pidfile = open(pidfile_path, "r")
except IOError: except IOError:
pass pass
else: else:
@@ -156,10 +154,10 @@ def write_pid_to_pidfile(pidfile_path):
and write it to the named file as a line of text. and write it to the named file as a line of text.
""" """
open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY) open_flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
open_mode = 0o644 open_mode = 0o644
pidfile_fd = os.open(pidfile_path, open_flags, open_mode) pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
pidfile = os.fdopen(pidfile_fd, 'w') pidfile = os.fdopen(pidfile_fd, "w")
# According to the FHS 2.3 section on PID files in /var/run: # According to the FHS 2.3 section on PID files in /var/run:
# #
+46 -40
View File
@@ -27,6 +27,7 @@ class SQLiteLockFile(LockBase):
if SQLiteLockFile.testdb is None: if SQLiteLockFile.testdb is None:
import tempfile import tempfile
_fd, testdb = tempfile.mkstemp() _fd, testdb = tempfile.mkstemp()
os.close(_fd) os.close(_fd)
os.unlink(testdb) os.unlink(testdb)
@@ -34,20 +35,24 @@ class SQLiteLockFile(LockBase):
SQLiteLockFile.testdb = testdb SQLiteLockFile.testdb = testdb
import sqlite3 import sqlite3
self.connection = sqlite3.connect(SQLiteLockFile.testdb) self.connection = sqlite3.connect(SQLiteLockFile.testdb)
c = self.connection.cursor() c = self.connection.cursor()
try: try:
c.execute("create table locks" c.execute(
"(" "create table locks"
" lock_file varchar(32)," "("
" unique_name varchar(32)" " lock_file varchar(32),"
")") " unique_name varchar(32)"
")"
)
except sqlite3.OperationalError: except sqlite3.OperationalError:
pass pass
else: else:
self.connection.commit() self.connection.commit()
import atexit import atexit
atexit.register(os.unlink, SQLiteLockFile.testdb) atexit.register(os.unlink, SQLiteLockFile.testdb)
def acquire(self, timeout=None): def acquire(self, timeout=None):
@@ -68,32 +73,35 @@ class SQLiteLockFile(LockBase):
while True: while True:
if not self.is_locked(): if not self.is_locked():
# Not locked. Try to lock it. # Not locked. Try to lock it.
cursor.execute("insert into locks" cursor.execute(
" (lock_file, unique_name)" "insert into locks"
" values" " (lock_file, unique_name)"
" (?, ?)", " values"
(self.lock_file, self.unique_name)) " (?, ?)",
(self.lock_file, self.unique_name),
)
self.connection.commit() self.connection.commit()
# Check to see if we are the only lock holder. # Check to see if we are the only lock holder.
cursor.execute("select * from locks" cursor.execute(
" where unique_name = ?", "select * from locks" " where unique_name = ?", (self.unique_name,)
(self.unique_name,)) )
rows = cursor.fetchall() rows = cursor.fetchall()
if len(rows) > 1: if len(rows) > 1:
# Nope. Someone else got there. Remove our lock. # Nope. Someone else got there. Remove our lock.
cursor.execute("delete from locks" cursor.execute(
" where unique_name = ?", "delete from locks" " where unique_name = ?",
(self.unique_name,)) (self.unique_name,),
)
self.connection.commit() self.connection.commit()
else: else:
# Yup. We're done, so go home. # Yup. We're done, so go home.
return return
else: else:
# Check to see if we are the only lock holder. # Check to see if we are the only lock holder.
cursor.execute("select * from locks" cursor.execute(
" where unique_name = ?", "select * from locks" " where unique_name = ?", (self.unique_name,)
(self.unique_name,)) )
rows = cursor.fetchall() rows = cursor.fetchall()
if len(rows) == 1: if len(rows) == 1:
# We're the locker, so go home. # We're the locker, so go home.
@@ -103,9 +111,9 @@ class SQLiteLockFile(LockBase):
if timeout is not None and time.time() > end_time: if timeout is not None and time.time() > end_time:
if timeout > 0: if timeout > 0:
# No more waiting. # No more waiting.
raise LockTimeout("Timeout waiting to acquire" raise LockTimeout(
" lock for %s" % "Timeout waiting to acquire" " lock for %s" % self.path
self.path) )
else: else:
# Someone else has the lock and we are impatient.. # Someone else has the lock and we are impatient..
raise AlreadyLocked("%s is already locked" % self.path) raise AlreadyLocked("%s is already locked" % self.path)
@@ -117,40 +125,38 @@ class SQLiteLockFile(LockBase):
if not self.is_locked(): if not self.is_locked():
raise NotLocked("%s is not locked" % self.path) raise NotLocked("%s is not locked" % self.path)
if not self.i_am_locking(): if not self.i_am_locking():
raise NotMyLock("%s is locked, but not by me (by %s)" % raise NotMyLock(
(self.unique_name, self._who_is_locking())) "%s is locked, but not by me (by %s)"
% (self.unique_name, self._who_is_locking())
)
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("delete from locks" cursor.execute(
" where unique_name = ?", "delete from locks" " where unique_name = ?", (self.unique_name,)
(self.unique_name,)) )
self.connection.commit() self.connection.commit()
def _who_is_locking(self): def _who_is_locking(self):
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("select unique_name from locks" cursor.execute(
" where lock_file = ?", "select unique_name from locks" " where lock_file = ?", (self.lock_file,)
(self.lock_file,)) )
return cursor.fetchone()[0] return cursor.fetchone()[0]
def is_locked(self): def is_locked(self):
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("select * from locks" cursor.execute("select * from locks" " where lock_file = ?", (self.lock_file,))
" where lock_file = ?",
(self.lock_file,))
rows = cursor.fetchall() rows = cursor.fetchall()
return not not rows return not not rows
def i_am_locking(self): def i_am_locking(self):
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("select * from locks" cursor.execute(
" where lock_file = ?" "select * from locks" " where lock_file = ?" " and unique_name = ?",
" and unique_name = ?", (self.lock_file, self.unique_name),
(self.lock_file, self.unique_name)) )
return not not cursor.fetchall() return not not cursor.fetchall()
def break_lock(self): def break_lock(self):
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("delete from locks" cursor.execute("delete from locks" " where lock_file = ?", (self.lock_file,))
" where lock_file = ?",
(self.lock_file,))
self.connection.commit() self.connection.commit()
+9 -9
View File
@@ -3,8 +3,7 @@ from __future__ import absolute_import
import os import os
import time import time
from . import (LockBase, NotLocked, NotMyLock, LockTimeout, from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
AlreadyLocked)
class SymlinkLockFile(LockBase): class SymlinkLockFile(LockBase):
@@ -40,12 +39,11 @@ class SymlinkLockFile(LockBase):
# Otherwise the lock creation failed. # Otherwise the lock creation failed.
if timeout is not None and time.time() > end_time: if timeout is not None and time.time() > end_time:
if timeout > 0: if timeout > 0:
raise LockTimeout("Timeout waiting to acquire" raise LockTimeout(
" lock for %s" % "Timeout waiting to acquire" " lock for %s" % self.path
self.path) )
else: else:
raise AlreadyLocked("%s is already locked" % raise AlreadyLocked("%s is already locked" % self.path)
self.path)
time.sleep(timeout / 10 if timeout is not None else 0.1) time.sleep(timeout / 10 if timeout is not None else 0.1)
else: else:
# Link creation succeeded. We're good to go. # Link creation succeeded. We're good to go.
@@ -62,8 +60,10 @@ class SymlinkLockFile(LockBase):
return os.path.islink(self.lock_file) return os.path.islink(self.lock_file)
def i_am_locking(self): def i_am_locking(self):
return (os.path.islink(self.lock_file) return (
and os.readlink(self.lock_file) == self.unique_name) os.path.islink(self.lock_file)
and os.readlink(self.lock_file) == self.unique_name
)
def break_lock(self): def break_lock(self):
if os.path.islink(self.lock_file): # exists && link if os.path.islink(self.lock_file): # exists && link