From: Frank Brehm Date: Wed, 9 Jan 2019 16:55:05 +0000 (+0100) Subject: Removing lib/webhooks/lock_handler.py X-Git-Tag: 1.6.4^2~64 X-Git-Url: https://git.uhu-banane.net/?a=commitdiff_plain;h=1e7c6302faead9d67b0c4a637c0111d7907f8f04;p=pixelpark%2Fpuppetmaster-webhooks.git Removing lib/webhooks/lock_handler.py --- diff --git a/lib/webhooks/lock_handler.py b/lib/webhooks/lock_handler.py deleted file mode 100644 index f99a664..0000000 --- a/lib/webhooks/lock_handler.py +++ /dev/null @@ -1,1201 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -@author: Frank Brehm -@contact: frank.brehm@pixelpark.com -@copyright: © 2018 by Frank Brehm, Publicies Pixelpark GmbH, Berlin -@summary: Module for a extended handler module, which has additional - methods for locking -""" -from __future__ import absolute_import - -# Standard modules -import sys -import os -import logging -import time -import errno -import traceback -import datetime -import fcntl - -from numbers import Number - -# Third party modules -from six import reraise - -# Own modules -from fb_tools.common import to_utf8 -from fb_tools.errors import CouldntOccupyLockfileError, HandlerError -from fb_tools.obj import FbBaseObject -from fb_tools.handler import BaseHandler - -__version__ = '0.5.0' - -LOG = logging.getLogger(__name__) - -# Module variables -DEFAULT_LOCKRETRY_DELAY_START = 0.1 -DEFAULT_LOCKRETRY_DELAY_INCREASE = 0.2 -DEFAULT_LOCKRETRY_MAX_DELAY = 10 -DEFAULT_MAX_LOCKFILE_AGE = 300 -DEFAULT_LOCKING_USE_PID = True - - -# ============================================================================= -class LockHandlerError(HandlerError): - """ - Base exception class for all exceptions belonging to locking issues - in this module - """ - - pass - - -# ============================================================================= -class LockObjectError(LockHandlerError): - """ - Special exception class for exceptions raising inside methods of - the LockObject. - """ - - pass - - -# ============================================================================= -class LockdirNotExistsError(LockHandlerError): - """ - Exception class for the case, that the parent directory of the lockfile - (lockdir) doesn't exists. - """ - - # ------------------------------------------------------------------------- - def __init__(self, lockdir): - """ - Constructor. - - @param lockdir: the directory, wich doesn't exists. - @type lockdir: str - - """ - - self.lockdir = lockdir - - # ------------------------------------------------------------------------- - def __str__(self): - """Typecasting into a string for error output.""" - - return "Locking directory {!r} doesn't exists or is not a directory.".format(self.lockdir) - - -# ============================================================================= -class LockdirNotWriteableError(LockHandlerError): - """ - Exception class for the case, that the parent directory of the lockfile - (lockdir) isn't writeable for the current process. - """ - - # ------------------------------------------------------------------------- - def __init__(self, lockdir): - """ - Constructor. - - @param lockdir: the directory, wich isn't writeable - @type lockdir: str - - """ - - self.lockdir = lockdir - - # ------------------------------------------------------------------------- - def __str__(self): - """Typecasting into a string for error output.""" - - return "Locking directory {!r} isn't writeable.".format(self.lockdir) - - -# ============================================================================= -class LockObject(FbBaseObject): - """ - Capsulation class as a result of a successful lock action. It contains all - important informations about the lock. - - It can be used for holding these informations and, if desired, to remove - the lock automatically, if the current instance of LockObject is removed. - - """ - - # ------------------------------------------------------------------------- - def __init__( - self, lockfile, ctime=None, mtime=None, fcontent=None, fd=None, simulate=False, - autoremove=False, appname=None, verbose=0, version=__version__, - base_dir=None, silent=False): - """ - Initialisation of the LockObject object. - - @raise LockObjectError: on a uncoverable error. - - @param lockfile: the file, which represents the lock, must exists - @type lockfile: str - @param ctime: the creation time of the lockfile - @type ctime: datetime - @param mtime: the modification time of the lockfile - @type mtime: datetime - @param fcontent: the content of the lockfile - @type fcontent: str - @param fd: The numeric file descriptor of the lockfile, if opened, if not opened, then None - @type fd: int or None - @param simulate: don't execute actions, only display them - @type simulate: bool - @param autoremove: removing the lockfile on deleting the current object - @type autoremove: bool - @param appname: name of the current running application - @type appname: str - @param verbose: verbose level - @type verbose: int - @param version: the version string of the current object or application - @type version: str - @param base_dir: the base directory of all operations - @type base_dir: str - @param silent: Remove silently the lockfile (except on verbose level >= 2) - @type silent: bool - - @return: None - """ - - self._fd = None - - super(LockObject, self).__init__( - appname=appname, verbose=verbose, version=version, - base_dir=base_dir, initialized=False, - ) - - if not lockfile: - raise LockObjectError("No lockfile given on init of a LockObject object.") - - if not os.path.exists(lockfile): - raise LockObjectError("Lockfile {!r} doesn't exists.".format(lockfile)) - - if not os.path.isfile(lockfile): - raise LockObjectError("Lockfile {!r} is not a regular file.".format(lockfile)) - - if fd is not None: - self._fd = fd - - self._lockfile = os.path.realpath(lockfile) - - self._fcontent = None - if fcontent is not None: - self._fcontent = str(fcontent) - self._simulate = bool(simulate) - self._autoremove = bool(autoremove) - self._silent = bool(silent) - - self._ctime = ctime - self._mtime = mtime - - # Detecting self._ctime and self._mtime from filestat of the lockfile - if not self._ctime or not self._mtime: - fstat = os.stat(lockfile) - if not self._ctime: - self._ctime = datetime.datetime.utcfromtimestamp(fstat.st_ctime) - if not self._mtime: - self._mtime = datetime.datetime.utcfromtimestamp(fstat.st_mtime) - - self.initialized = True - - # ----------------------------------------------------------- - @property - def lockfile(self): - """The file, which represents the lock.""" - return self._lockfile - - # ----------------------------------------------------------- - @property - def ctime(self): - """The creation time of the lockfile.""" - return self._ctime - - # ----------------------------------------------------------- - @property - def mtime(self): - """The last modification time of the lockfile.""" - return self._mtime - - # ----------------------------------------------------------- - @property - def fcontent(self): - """The content of the lockfile.""" - return self._fcontent - - # ----------------------------------------------------------- - @property - def fd(self): - "The numeric file descriptor of the lockfile." - return self._fd - - # ----------------------------------------------------------- - @property - def simulate(self): - """Don't execute actions, only display them.""" - return self._simulate - - @simulate.setter - def simulate(self, value): - self._simulate = bool(value) - - # ----------------------------------------------------------- - @property - def autoremove(self): - """Removing the lockfile on deleting the current object.""" - return self._autoremove - - @autoremove.setter - def autoremove(self, value): - self._autoremove = bool(value) - - # ----------------------------------------------------------- - @property - def silent(self): - """Remove silently the lockfile (except on verbose level >= 2).""" - return self._silent - - @silent.setter - def silent(self, value): - self._silent = bool(value) - - # ------------------------------------------------------------------------- - def as_dict(self, short=True): - """ - Transforms the elements of the object into a dict - - @param short: don't include local properties in resulting dict. - @type short: bool - - @return: structure as dict - @rtype: dict - """ - - res = super(LockObject, self).as_dict(short=short) - res['lockfile'] = self.lockfile - res['ctime'] = self.ctime - res['mtime'] = self.mtime - res['fcontent'] = self.fcontent - res['simulate'] = self.simulate - res['autoremove'] = self.autoremove - res['silent'] = self.silent - res['fd'] = self.fd - - return res - - # ------------------------------------------------------------------------- - def __repr__(self): - """Typecasting into a string for reproduction.""" - - out = super(LockObject, self).__repr__()[:-2] - - fields = [] - fields.append("lockfile={!r}".format(self.lockfile)) - if self.fcontent: - fields.append("fcontent={!r}".format(self.fcontent)) - fields.append("ctime={!r}".format(self.ctime)) - fields.append("mtime={!r}".format(self.mtime)) - fields.append("fcontent={!r}".format(self.fcontent)) - fields.append("fd={!r}".format(self.fd)) - fields.append("simulate={!r}".format(self.simulate)) - fields.append("autoremove={!r}".format(self.autoremove)) - fields.append("silent={!r}".format(self.silent)) - - if fields: - out += ', ' + ", ".join(fields) - out += ")>" - return out - - # ------------------------------------------------------------------------- - def __del__(self): - """Destructor. - - Removes the lockfile, if self.autoremove is True - - """ - - if not getattr(self, '_initialized', False): - return - - if self.fd is not None: - msg = "Closing file descriptor {} ...".format(self.fd) - if self.silent: - if self.verbose >= 2: - LOG.debug(msg) - else: - LOG.debug(msg) - os.close(self.fd) - self._fd = None - - if self.autoremove and self.exists: - - msg = "Automatic removing of {!r} ...".format(self.lockfile) - if self.silent: - if self.verbose >= 2: - LOG.debug(msg) - else: - LOG.info(msg) - - if not self.simulate: - os.remove(self.lockfile) - - # ------------------------------------------------------------------------- - def exists(self): - """Returns, whether the lockfile exists or not.""" - - if self.simulate: - return True - - return os.path.exists(self.lockfile) - - # ------------------------------------------------------------------------- - def refresh(self): - """ - Refreshes the atime and mtime of the lockfile to the current time. - """ - - msg = "Refreshing atime and mtime of {!r} to the current timestamp.".format(self.lockfile) - LOG.debug(msg) - - if not self.simulate: - os.utime(self.lockfile, None) - - self._mtime = datetime.datetime.utcnow() - - -# ============================================================================= -class LockHandler(BaseHandler): - """ - Handler class with additional properties and methods to create, - check and remove lock files. - """ - - # ------------------------------------------------------------------------- - def __init__( - self, lockdir=None, - lockretry_delay_start=DEFAULT_LOCKRETRY_DELAY_START, - lockretry_delay_increase=DEFAULT_LOCKRETRY_DELAY_INCREASE, - lockretry_max_delay=DEFAULT_LOCKRETRY_MAX_DELAY, - max_lockfile_age=DEFAULT_MAX_LOCKFILE_AGE, - locking_use_pid=DEFAULT_LOCKING_USE_PID, - stay_opened=True, appname=None, verbose=0, version=__version__, base_dir=None, - simulate=False, sudo=False, quiet=False, silent=False, *targs, **kwargs): - """ - Initialisation of the locking handler object. - - @raise LockdirNotExistsError: if the lockdir (or base_dir) doesn't exists - @raise LockHandlerError: on a uncoverable error. - - @param lockdir: a special directory for searching and creating the - lockfiles, if not given, self.base_dir will used - @type lockdir: str - @param lockretry_delay_start: the first delay in seconds after an - unsuccessful lockfile creation - @type lockretry_delay_start: Number - @param lockretry_delay_increase: seconds to increase the delay in every - wait cycle - @type lockretry_delay_increase: Number - @param lockretry_max_delay: the total maximum delay in seconds for - trying to create a lockfile - @type lockretry_max_delay: Number - @param max_lockfile_age: the maximum age of the lockfile (in seconds), - for the existing lockfile is valid (if - locking_use_pid is False). - @type max_lockfile_age: Number - @param locking_use_pid: write the PID of creating process into the - fresh created lockfile, if False, the lockfile - will be leaved empty, the PID in the lockfile - can be used to check the validity of the - lockfile - @type locking_use_pid: bool - @param stay_opened: should the lockfile stay opened after creation - @@type stay_opened: bool - @param appname: name of the current running application - @type appname: str - @param verbose: verbose level - @type verbose: int - @param version: the version string of the current object or application - @type version: str - @param base_dir: the base directory of all operations - @type base_dir: str - @param simulate: don't execute actions, only display them - @type simulate: bool - @param sudo: should the command executed by sudo by default - @type sudo: bool - @param quiet: don't display ouput of action after calling - @type quiet: bool - @param silent: Create and remove silently the lockfile (except on verbose level >= 2) - @type silent: bool - - @return: None - - """ - - self._stay_opened = bool(stay_opened) - - super(LockHandler, self).__init__( - appname=appname, verbose=verbose, version=version, base_dir=base_dir, - initialized=False, simulate=simulate, sudo=sudo, quiet=quiet, - ) - - self._lockdir = None - if lockdir is not None: - self.lockdir = lockdir - - self._lockretry_delay_start = DEFAULT_LOCKRETRY_DELAY_START - self.lockretry_delay_start = lockretry_delay_start - - self._lockretry_delay_increase = DEFAULT_LOCKRETRY_DELAY_INCREASE - self.lockretry_delay_increase = lockretry_delay_increase - - self._lockretry_max_delay = DEFAULT_LOCKRETRY_MAX_DELAY - self.lockretry_max_delay = lockretry_max_delay - - self._max_lockfile_age = DEFAULT_MAX_LOCKFILE_AGE - self.max_lockfile_age = max_lockfile_age - - self._locking_use_pid = DEFAULT_LOCKING_USE_PID - self.locking_use_pid = locking_use_pid - - self._silent = bool(silent) - - # ----------------------------------------------------------- - @property - def lockdir(self): - """The directory for searching and creating the lockfiles.""" - if self._lockdir: - return self._lockdir - return self.base_dir - - @lockdir.setter - def lockdir(self, value): - if not value: - self._lockdir = None - return - - if os.path.isabs(value): - self._lockdir = os.path.normpath(value) - else: - self._lockdir = os.path.normpath(os.path.join(self.base_dir, value)) - - # ----------------------------------------------------------- - @property - def lockretry_delay_start(self): - """ - The first delay in seconds after an unsuccessful lockfile creation. - """ - return self._lockretry_delay_start - - @lockretry_delay_start.setter - def lockretry_delay_start(self, value): - if not isinstance(value, Number): - msg = "Value {val!r} for {what} is not a Number.".format( - val=value, what='lockretry_delay_start') - raise LockHandlerError(msg) - - if value <= 0: - msg = "The value for {what} must be greater than zero (is {val!r}).".format( - val=value, what='lockretry_delay_start') - raise LockHandlerError(msg) - - self._lockretry_delay_start = value - - # ----------------------------------------------------------- - @property - def lockretry_delay_increase(self): - """ - The seconds to increase the delay in every wait cycle. - """ - return self._lockretry_delay_increase - - @lockretry_delay_increase.setter - def lockretry_delay_increase(self, value): - if not isinstance(value, Number): - msg = "Value {val!r} for {what} is not a Number.".format( - val=value, what='lockretry_delay_increase') - raise LockHandlerError(msg) - - if value < 0: - msg = "The value for {what} must be greater than zero (is {val!r}).".format( - val=value, what='lockretry_delay_increase') - raise LockHandlerError(msg) - - self._lockretry_delay_increase = value - - # ----------------------------------------------------------- - @property - def lockretry_max_delay(self): - """ - The total maximum delay in seconds for trying to create a lockfile. - """ - return self._lockretry_max_delay - - @lockretry_max_delay.setter - def lockretry_max_delay(self, value): - if not isinstance(value, Number): - msg = "Value {val!r} for {what} is not a Number.".format( - val=value, what='lockretry_max_delay') - raise LockHandlerError(msg) - - if value <= 0: - msg = "The value for {what} must be greater than zero (is {val!r}).".format( - val=value, what='lockretry_max_delay') - raise LockHandlerError(msg) - - self._lockretry_max_delay = value - - # ----------------------------------------------------------- - @property - def max_lockfile_age(self): - """ - The maximum age of the lockfile (in seconds), for the existing lockfile - is valid (if locking_use_pid is False). - """ - return self._max_lockfile_age - - @max_lockfile_age.setter - def max_lockfile_age(self, value): - if not isinstance(value, Number): - msg = "Value {val!r} for {what} is not a Number.".format( - val=value, what='max_lockfile_age') - raise LockHandlerError(msg) - - if value <= 0: - msg = "The value for {what} must be greater than zero (is {val!r}).".format( - val=value, what='max_lockfile_age') - raise LockHandlerError(msg) - - self._max_lockfile_age = value - - # ----------------------------------------------------------- - @property - def locking_use_pid(self): - """ - Write the PID of creating process into the fresh created lockfile. - """ - return self._locking_use_pid - - @locking_use_pid.setter - def locking_use_pid(self, value): - self._locking_use_pid = bool(value) - - # ----------------------------------------------------------- - @property - def stay_opened(self): - """ - Should the lockfile stay opened after creation. If yes, then it will be closed - on deleting the LockObject. - """ - return self._stay_opened - - @stay_opened.setter - def stay_opened(self, value): - self._stay_opened = bool(value) - - # ----------------------------------------------------------- - @property - def silent(self): - """Create and remove silently the lockfile (except on verbose level >= 2).""" - return self._silent - - @silent.setter - def silent(self, value): - self._silent = bool(value) - - # ------------------------------------------------------------------------- - def as_dict(self, short=True): - """ - Transforms the elements of the object into a dict - - @param short: don't include local properties in resulting dict. - @type short: bool - - @return: structure as dict - @rtype: dict - """ - - res = super(LockHandler, self).as_dict(short=short) - res['lockdir'] = self.lockdir - res['lockretry_delay_start'] = self.lockretry_delay_start - res['lockretry_delay_increase'] = self.lockretry_delay_increase - res['lockretry_max_delay'] = self.lockretry_max_delay - res['max_lockfile_age'] = self.max_lockfile_age - res['locking_use_pid'] = self.locking_use_pid - res['silent'] = self.silent - res['stay_opened'] = self.stay_opened - - return res - - # ------------------------------------------------------------------------- - def __repr__(self): - """Typecasting into a string for reproduction.""" - - out = super(LockHandler, self).__repr__()[:-2] - - fields = [] - if self._lockdir: - fields.append("lockdir=%r" % (self.lockdir)) - fields.append("lockretry_delay_start=%r" % (self.lockretry_delay_start)) - fields.append("lockretry_delay_increase=%r" % (self.lockretry_delay_increase)) - fields.append("lockretry_max_delay=%r" % (self.lockretry_max_delay)) - fields.append("max_lockfile_age=%r" % (self.max_lockfile_age)) - fields.append("locking_use_pid=%r" % (self.locking_use_pid)) - fields.append("silent=%r" % (self.silent)) - fields.append("stay_opened=%r" % (self.stay_opened)) - - if fields: - out += ', ' + ", ".join(fields) - out += ")>" - return out - - # ------------------------------------------------------------------------- - def check_for_number(self, value, default, what, must_gt_zero=False, must_ge_zero=False): - - if value is None: - return default - - if not isinstance(value, Number): - msg = "Value {val!r} for {what} is not a Number.".format( - val=value, what=what) - raise LockHandlerError(msg) - - if must_gt_zero and value <= 0: - msg = "The value for {what} must be greater than zero (is {val!r}).".format( - val=value, what=what) - raise LockHandlerError(msg) - - if must_ge_zero and value < 0: - msg = ( - "The value for {what} must be greater than " - "or equal to zero (is {val!r}).").format( - val=value, what=what) - raise LockHandlerError(msg) - - return value - - # ------------------------------------------------------------------------- - def create_lockfile( - self, lockfile, delay_start=None, delay_increase=None, max_delay=None, - use_pid=None, max_age=None, pid=None, raise_on_fail=True, stay_opened=None): - """ - Tries to create the given lockfile exclusive. - - If the lockfile exists and is valid, it waits a total maximum - of max_delay seconds an increasing amount of seconds to get exclusive - access to the lockfile. - - @raise CouldntOccupyLockfileError: if the lockfile couldn't occupied - and raise_on_fail is set to True - - @param lockfile: the lockfile to use as a semaphore, if not given - as an absolute path, it will be supposed to be - relative to self.lockdir. - @type lockfile: str - @param delay_start: the first delay in seconds after an unsuccessful - lockfile creation, if not given, - self.lockretry_delay_start will used. - @type delay_start: Number (or None) - @param delay_increase: seconds to increase the delay in every wait - cycle, if not given, self.lockretry_delay_increase - will used. - @type delay_increase: Number - @param max_delay: the total maximum delay in seconds for trying - to create a lockfile, if not given, - self.lockretry_max_delay will used. - @type max_delay: Number - @param use_pid: write the PID of creating process into the fresh - created lockfile, if not given, self.locking_use_pid - will used. - @type use_pid: bool - @param max_age: the maximum age of the lockfile (in seconds), for the - existing lockfile is valid (if locking_use_pid is False). - @type max_age: Number - @param pid: the pid to write into the lockfile, if use_pid is set - to True, if not given, the PID of the current process is used. - @type pid: int - @param raise_on_fail: raise an exception instead of returning False, if - the lockfile couldn't occupied. - @type raise_on_fail: bool - - @param stay_opened: should the lockfile stay opened after creation, - @@type stay_opened: bool or None - - @return: a lock object on success, else None - @rtype: LockObject or None - - """ - - delay_start = self.check_for_number( - delay_start, self.lockretry_delay_start, - what='delay_start', must_gt_zero=True) - - delay_increase = self.check_for_number( - delay_increase, self.lockretry_delay_increase, - what='delay_increase', must_ge_zero=True) - - max_delay = self.check_for_number( - max_delay, self.lockretry_max_delay, - what='max_delay', must_ge_zero=True) - - if use_pid is None: - use_pid = self.locking_use_pid - else: - use_pid = bool(use_pid) - - max_age = self.check_for_number( - max_age, self.max_lockfile_age, - what='max_age', must_ge_zero=True) - - if pid is None: - pid = os.getpid() - else: - pid = int(pid) - if pid <= 0: - msg = "Invalid PID {} given on calling create_lockfile().".format(pid) - raise LockHandlerError(msg) - - if os.path.isabs(lockfile): - lockfile = os.path.normpath(lockfile) - else: - lockfile = os.path.normpath(os.path.join(self.lockdir, lockfile)) - - lockdir = os.path.dirname(lockfile) - LOG.debug("Trying to lock lockfile {!r} ...".format(lockfile)) - if self.verbose > 1: - LOG.debug("Using lock directory {!r} ...".format(lockdir)) - - if not os.path.isdir(lockdir): - raise LockdirNotExistsError(lockdir) - - if not os.access(lockdir, os.W_OK): - msg = "Locking directory {!r} isn't writeable.".format(lockdir) - if self.simulate: - LOG.error(msg) - else: - raise LockdirNotWriteableError(lockdir) - - if stay_opened is None: - stay_opened = self.stay_opened - else: - stay_opened = bool(stay_opened) - - return self._do_create_lockfile( - lockfile=lockfile, delay_start=delay_start, max_delay=max_delay, max_age=max_age, - delay_increase=delay_increase, pid=pid, use_pid=use_pid, raise_on_fail=raise_on_fail, - stay_opened=stay_opened) - - # ------------------------------------------------------------------------- - def _do_create_lockfile( - self, lockfile, delay_start, max_delay, max_age, delay_increase, - pid, use_pid, raise_on_fail, stay_opened): - - counter = 0 - delay = delay_start - - fd = None - time_diff = 0 - start_time = time.time() - - ctime = None - mtime = None - - # Big try block to ensure closing open file descriptor - try: - - # Big loop on trying to create the lockfile - while fd is None and time_diff < max_delay: - - time_diff = time.time() - start_time - counter += 1 - - if self.verbose > 3: - LOG.debug("Current time difference: {:0.3f} seconds.".format(time_diff)) - if time_diff >= max_delay: - break - - # Try creating lockfile exclusive - LOG.debug("Try {try_nr} on creating lockfile {lfile!r} ...".format( - try_nr=counter, lfile=lockfile)) - ctime = datetime.datetime.utcnow() - fd = self._create_lockfile(lockfile) - if fd is not None: - # success, then exit - break - - # Check for other process, using this lockfile - if not self.check_lockfile(lockfile, max_age, use_pid): - # No other process is using this lockfile - if os.path.exists(lockfile): - LOG.info("Removing lockfile {!r} ...".format(lockfile)) - try: - if not self.simulate: - os.remove(lockfile) - except Exception as e: - msg = "Error on removing lockfile {lfile!r): {err}".format( - lfile=lockfile, err=e) - LOG.error(msg) - time.sleep(delay) - delay += delay_increase - continue - - fd = self._create_lockfile(lockfile) - if fd: - break - - # No success, then retry later - if self.verbose > 2: - LOG.debug("Sleeping for {:0.1f} seconds.".format(float(delay))) - time.sleep(delay) - delay += delay_increase - - # fd is either None, for no success on locking - if fd is None: - time_diff = time.time() - start_time - e = CouldntOccupyLockfileError(lockfile, time_diff, counter) - if raise_on_fail: - raise e - else: - LOG.error(str(e)) - return None - - # or an int for success - msg = "Got a lock for lockfile {!r}.".format(lockfile) - if self.silent: - LOG.debug(msg) - else: - LOG.info(msg) - out = to_utf8("{}\n".format(pid)) - LOG.debug("Write {what!r} in lockfile {lfile!r} ...".format( - what=out, lfile=lockfile)) - - finally: - - if fd is not None and not self.simulate: - os.write(fd, out) - - if stay_opened: - LOG.debug("Seeking and syncing {!r} ...".format(lockfile)) - os.lseek(fd, 0, 0) - os.fsync(fd) - else: - LOG.debug("Closing {!r} ...".format(lockfile)) - os.close(fd) - fd = None - - if fd is not None and self.simulate: - fd = None - - mtime = datetime.datetime.utcnow() - - lock_object = LockObject( - lockfile, ctime=ctime, mtime=mtime, fcontent=out, fd=fd, simulate=self.simulate, - appname=self.appname, verbose=self.verbose, base_dir=self.base_dir, silent=self.silent, - ) - - return lock_object - - # ------------------------------------------------------------------------- - def _create_lockfile(self, lockfile): - """ - Handles exclusive creation of a lockfile. - - @return: a file decriptor of the opened lockfile (if possible), - or None, if it isn't. - @rtype: int or None - - """ - - if self.verbose > 1: - LOG.debug("Trying to open {!r} exclusive ...".format(lockfile)) - if self.simulate: - LOG.debug("Simulation mode, no real creation of a lockfile.") - return -1 - - fd = None - try: - fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) - fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except OSError as e: - msg = "Error on creating lockfile {lfile!r}: {err}".format( - lfile=lockfile, err=e) - if e.errno == errno.EEXIST: - LOG.debug(msg) - return None - else: - error_tuple = sys.exc_info() - reraise(LockHandlerError, msg, error_tuple[2]) - - return fd - - # ------------------------------------------------------------------------- - def remove_lockfile(self, lockfile): - """ - Removing lockfile. - - @param lockfile: the lockfile to remove. - @type lockfile: str - - @return: the lockfile was removed (or not) - @rtype: bool - - """ - - if os.path.isabs(lockfile): - lockfile = os.path.normpath(lockfile) - else: - lockfile = os.path.normpath(os.path.join(self.lockdir, lockfile)) - - if not os.path.exists(lockfile): - LOG.debug("Lockfile {!r} to remove doesn't exists.".format(lockfile)) - return True - - LOG.info("Removing lockfile {!r} ...".format(lockfile)) - if self.simulate: - LOG.debug("Simulation mode - lockfile won't removed.") - return True - - try: - os.remove(lockfile) - except Exception as e: - LOG.error("Error on removing lockfile {lfile!r}: {err}".format(lfile=lockfile, err=e)) - if self.verbose: - tb = traceback.format_exc() - LOG.debug("Stacktrace:\n" + tb) - return False - - return True - - # ------------------------------------------------------------------------- - def check_lockfile(self, lockfile, max_age=None, use_pid=None): - """ - Checks the validity of the given lockfile. - - If use_pid is True, and there is a PID inside the lockfile, then - this PID is checked for a running process. - If use_pid is not True, then the age of the lockfile is checked - against the parameter max_age. - - @param lockfile: the lockfile to check - @type lockfile: str - @param max_age: the maximum age of the lockfile (in seconds), for - this lockfile is valid (if use_pid is False). - @type max_age: int - @param use_pid: check the content of the lockfile for a PID - of a running process - @type use_pid: bool - - @return: Validity of the lockfile (PID exists and shows to a - running process or the lockfile is not too old). - Returns False, if the lockfile is not existing, contains an - invalid PID or is too old. - @rtype: bool - - """ - - if use_pid is None: - use_pid = self.locking_use_pid - else: - use_pid = bool(use_pid) - - if max_age is None: - max_age = self.max_lockfile_age - else: - if not isinstance(max_age, Number): - msg = "Value {val!r} for {what} is not a Number.".format( - val=max_age, what='max_age') - raise LockHandlerError(msg) - if max_age <= 0: - msg = "The value for {what} must be greater than zero (is {val!r}).".format( - val=max_age, what='max_age') - raise LockHandlerError(msg) - - LOG.debug("Checking lockfile {!r} ...".format(lockfile)) - - if not os.path.exists(lockfile): - if self.verbose > 2: - LOG.debug("Lockfile {!r} doesn't exists.".format(lockfile)) - return False - - if not os.access(lockfile, os.R_OK): - LOG.warn("No read access for lockfile {!r}.".format(lockfile)) - return True - - if not os.access(lockfile, os.W_OK): - LOG.warn("No write access for lockfile {!r}.".format(lockfile)) - return True - - if use_pid: - pid = self.get_pid_from_file(lockfile, True) - if pid is None: - LOG.warn("Unusable lockfile {!r}.".format(lockfile)) - else: - if self.dead(pid): - LOG.warn("Process with PID {} is unfortunately dead.".format(pid)) - return False - else: - LOG.debug("Process with PID {} is still running.".format(pid)) - return True - - fstat = None - try: - fstat = os.stat(lockfile) - except OSError as e: - if e.errno == errno.ENOENT: - LOG.info("Could not stat for file {lfile!r}: {err}".format( - lfile=lockfile, err=e.strerror)) - return False - raise - - age = time.time() - fstat.st_mtime - if age >= max_age: - LOG.debug("Lockfile {lfile!r} is older than {max} seconds ({age} seconds).".format( - lfile=lockfile, max=max_age, age=age)) - return False - msg = "Lockfile {lfile!r} is {age} seconds old, but not old enough ({max}seconds).".format( - lfile=lockfile, max=max_age, age=age) - LOG.debug(msg) - return True - - # ------------------------------------------------------------------------- - def get_pid_from_file(self, pidfile, force=False): - """ - Tries to read the PID of some process from the given file. - - @raise LockHandlerError: if the pidfile could not be read - - @param pidfile: The file, where the PID should be in. - @type pidfile: str - @param force: Don't raise an exception, if something is going wrong. - Only return None. - @type force: bool - - @return: PID from pidfile - @rtype: int (or None) - - """ - - if self.verbose > 1: - LOG.debug("Trying to open pidfile {!r} ...".format(pidfile)) - try: - fh = open(pidfile, "rb") - except Exception as e: - msg = "Could not open pidfile {!r} for reading: ".format(pidfile) - msg += str(e) - if force: - LOG.warn(msg) - return None - else: - raise LockHandlerError(str(e)) - - content = fh.readline() - fh.close() - - content = content.strip() - if content == "": - msg = "First line of pidfile {!r} was empty.".format(pidfile) - if force: - LOG.warn(msg) - return None - else: - raise LockHandlerError(msg) - - pid = None - try: - pid = int(content) - except Exception as e: - msg = "Could not interprete {cont!r} as a PID from {file!r}: {err}".format( - cont=content, file=pidfile, err=e) - if force: - LOG.warn(msg) - return None - else: - raise LockHandlerError(msg) - - if pid <= 0: - msg = "Invalid PID {pid} in {file!r} found.".format(pid=pid, file=pidfile) - if force: - LOG.warn(msg) - return None - else: - raise LockHandlerError(msg) - - return pid - - # ------------------------------------------------------------------------- - def kill(self, pid, signal=0): - """ - Sends a signal to a process. - - @raise OSError: on some unpredictable errors - - @param pid: the PID of the process - @type pid: int - @param signal: the signal to send to the process, if the signal is 0 - (the default), no real signal is sent to the process, - it will only checked, whether the process is dead or not - @type signal: int - - @return: the process is dead or not - @rtype: bool - - """ - - try: - return os.kill(pid, signal) - except OSError as e: - # process is dead - if e.errno == errno.ESRCH: - return True - # no permissions - elif e.errno == errno.EPERM: - return False - else: - # reraise the error - raise - - # ------------------------------------------------------------------------- - def dead(self, pid): - """ - Gives back, whether the process with the given pid is dead - - @raise OSError: on some unpredictable errors - - @param pid: the PID of the process to check - @type pid: int - - @return: the process is dead or not - @rtype: bool - - """ - - if self.kill(pid): - return True - - # maybe the pid is a zombie that needs us to wait4 it - from os import waitpid, WNOHANG - - try: - dead = waitpid(pid, WNOHANG)[0] - except OSError as e: - # pid is not a child - if e.errno == errno.ECHILD: - return False - else: - raise - - return dead - - -# ============================================================================= -if __name__ == "__main__": - - pass - -# ============================================================================= - -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4