]> Frank Brehm's Git Trees - pixelpark/puppetmaster-webhooks.git/commitdiff
Fatei lib/webhooks/lock_handler.py fuer LockObject und LockHandler dazu
authorFrank Brehm <frank.brehm@pixelpark.com>
Fri, 9 Mar 2018 14:06:25 +0000 (15:06 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Fri, 9 Mar 2018 14:06:25 +0000 (15:06 +0100)
lib/webhooks/lock_handler.py [new file with mode: 0644]

diff --git a/lib/webhooks/lock_handler.py b/lib/webhooks/lock_handler.py
new file mode 100644 (file)
index 0000000..04b27d7
--- /dev/null
@@ -0,0 +1,1140 @@
+#!/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
+
+from numbers import Number
+
+# Third party modules
+from six import reraise
+
+# Own modules
+from .common import to_utf8
+
+from .errors import CouldntOccupyLockfileError
+
+from .obj import BaseObject
+
+from .handler import BaseHandlerError, BaseHandler
+
+__version__ = '0.1.1'
+
+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(BaseHandlerError):
+    """
+    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(BaseObject):
+    """
+    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, 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 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
+        """
+
+        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))
+
+        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 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
+
+        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("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.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,
+            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 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
+
+        """
+
+        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 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
+
+        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))
+
+        if fields:
+            out += ', ' + ", ".join(fields)
+        out += ")>"
+        return out
+
+    # -------------------------------------------------------------------------
+    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):
+        """
+        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
+
+        @return: a lock object on success, else None
+        @rtype: LockObject or None
+
+        """
+
+        if delay_start is None:
+            delay_start = self.lockretry_delay_start
+        else:
+            if not isinstance(delay_start, Number):
+                msg = "Value {val!r} for {what} is not a Number.".format(
+                    val=delay_start, what='delay_start')
+                raise LockHandlerError(msg)
+            if delay_start <= 0:
+                msg = "The value for {what} must be greater than zero (is {val!r}).".format(
+                    val=delay_start, what='delay_start')
+                raise LockHandlerError(msg)
+
+        if delay_increase is None:
+            delay_increase = self.lockretry_delay_increase
+        else:
+            if not isinstance(delay_increase, Number):
+                msg = "Value {val!r} for {what} is not a Number.".format(
+                    val=delay_increase, what='delay_increase')
+                raise LockHandlerError(msg)
+            if delay_increase < 0:
+                msg = (
+                    "The value for {what} must be greater than "
+                    "or equal to zero (is {val!r}).").format(
+                    val=delay_increase, what='delay_increase')
+                raise LockHandlerError(msg)
+
+        if max_delay is None:
+            max_delay = self.lockretry_max_delay
+        else:
+            if not isinstance(max_delay, Number):
+                msg = "Value {val!r} for {what} is not a Number.".format(
+                    val=max_delay, what='max_delay')
+                raise LockHandlerError(msg)
+            if max_delay <= 0:
+                msg = "The value for {what} must be greater than zero (is {val!r}).".format(
+                    val=max_delay, what='max_delay')
+                raise LockHandlerError(msg)
+            pass
+
+        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)
+
+        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)
+
+        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(msg)
+                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)
+                os.close(fd)
+
+        fd = None
+
+        mtime = datetime.datetime.utcnow()
+
+        lock_object = LockObject(
+            lockfile, ctime=ctime, mtime=mtime, fcontent=out, 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)
+        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