]> Frank Brehm's Git Trees - pixelpark/puppetmaster-webhooks.git/commitdiff
Adding possibility to stay a lockfile opened.
authorFrank Brehm <frank.brehm@pixelpark.com>
Fri, 9 Mar 2018 15:55:03 +0000 (16:55 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Fri, 9 Mar 2018 15:55:03 +0000 (16:55 +0100)
lib/webhooks/lock_handler.py
test/test_lock.py

index 04b27d73fdeeecac02cbca916a924f1481fbf671..3a7e8bc3799a050de75ddd55a248122e801cf37e 100644 (file)
@@ -17,6 +17,7 @@ import time
 import errno
 import traceback
 import datetime
+import fcntl
 
 from numbers import Number
 
@@ -32,7 +33,7 @@ from .obj import BaseObject
 
 from .handler import BaseHandlerError, BaseHandler
 
-__version__ = '0.1.1'
+__version__ = '0.2.1'
 
 log = logging.getLogger(__name__)
 
@@ -129,7 +130,7 @@ class LockObject(BaseObject):
 
     # -------------------------------------------------------------------------
     def __init__(
-        self, lockfile, ctime=None, mtime=None, fcontent=None, simulate=False,
+        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):
         """
@@ -145,6 +146,8 @@ class LockObject(BaseObject):
         @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
@@ -163,6 +166,8 @@ class LockObject(BaseObject):
         @return: None
         """
 
+        self._fd = None
+
         super(LockObject, self).__init__(
             appname=appname, verbose=verbose, version=version,
             base_dir=base_dir, initialized=False,
@@ -177,6 +182,9 @@ class LockObject(BaseObject):
         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
@@ -223,6 +231,12 @@ class LockObject(BaseObject):
         """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):
@@ -273,6 +287,7 @@ class LockObject(BaseObject):
         res['simulate'] = self.simulate
         res['autoremove'] = self.autoremove
         res['silent'] = self.silent
+        res['fd'] = self.fd
 
         return res
 
@@ -289,6 +304,7 @@ class LockObject(BaseObject):
         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))
@@ -309,6 +325,16 @@ class LockObject(BaseObject):
         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)
@@ -360,7 +386,7 @@ class LockHandler(BaseHandler):
             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,
+            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.
@@ -390,6 +416,8 @@ class LockHandler(BaseHandler):
                                 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
@@ -411,6 +439,8 @@ class LockHandler(BaseHandler):
 
         """
 
+        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,
@@ -557,6 +587,19 @@ class LockHandler(BaseHandler):
     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):
@@ -587,6 +630,7 @@ class LockHandler(BaseHandler):
         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
 
@@ -605,6 +649,7 @@ class LockHandler(BaseHandler):
         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)
@@ -614,7 +659,7 @@ class LockHandler(BaseHandler):
     # -------------------------------------------------------------------------
     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):
+            use_pid=None, max_age=None, pid=None, raise_on_fail=True, stay_opened=None):
         """
         Tries to create the given lockfile exclusive.
 
@@ -655,6 +700,9 @@ class LockHandler(BaseHandler):
                               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
 
@@ -744,6 +792,11 @@ class LockHandler(BaseHandler):
             else:
                 raise LockdirNotWriteableError(lockdir)
 
+        if stay_opened is None:
+            stay_opened = self.stay_opened
+        else:
+            stay_opened = bool(stay_opened)
+
         counter = 0
         delay = delay_start
 
@@ -827,14 +880,21 @@ class LockHandler(BaseHandler):
 
             if fd is not None and not self.simulate:
                 os.write(fd, out)
-                os.close(fd)
 
-        fd = None
+                if stay_opened:
+                    os.lseek(fd, 0, 0)
+                    os.fsync(fd)
+                else:
+                    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, simulate=self.simulate,
+            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,
         )
 
@@ -860,6 +920,7 @@ class LockHandler(BaseHandler):
         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)
index 8d7fe83d041e1d80c137ad42fcacd7d934b3b638..76c87f7013c5a5cd71d0a77ed546ecef04d7ea08 100755 (executable)
@@ -49,7 +49,7 @@ class TestLockHandler(WebHooksTestcase):
     # -------------------------------------------------------------------------
     def create_lockfile(self, content):
 
-        (fd, filename) = tempfile.mkstemp()
+        (fd, filename) = tempfile.mkstemp(suffix='.lock', prefix='test-', dir=self.lock_dir)
 
         LOG.debug("Created temporary file %r, writing in it.", filename)
         content = to_utf8(str(content))
@@ -136,8 +136,9 @@ class TestLockHandler(WebHooksTestcase):
             lockdir=self.lock_dir,
         )
         LOG.debug("Creating lockfile %r ...", self.lock_file)
-        locker.create_lockfile(self.lock_basename)
+        lock = locker.create_lockfile(self.lock_basename)
         LOG.debug("Removing lockfile %r ...", self.lock_file)
+        lock = None
         locker.remove_lockfile(self.lock_basename)
 
     # -------------------------------------------------------------------------
@@ -156,7 +157,8 @@ class TestLockHandler(WebHooksTestcase):
             LOG.debug("Creating lockfile %r ...", self.lock_file)
             lock = locker.create_lockfile(self.lock_basename)
             LOG.debug("PbLock object %%r: %r", lock)
-            LOG.debug("PbLock object %%s: %s", str(lock))
+            LOG.debug("PbLock object %%s:\n%s", str(lock))
+            lock = None
         finally:
             LOG.debug("Removing lockfile %r ...", self.lock_file)
             locker.remove_lockfile(self.lock_basename)
@@ -189,6 +191,7 @@ class TestLockHandler(WebHooksTestcase):
             tdiff = mtime2 - mtime1
             LOG.debug("Got a time difference between mtimes of %0.3f seconds." % (tdiff))
             self.assertGreater(mtime2, mtime1)
+            lock = None
         finally:
             LOG.debug("Removing lockfile %r ...", self.lock_file)
             locker.remove_lockfile(self.lock_basename)
@@ -209,7 +212,8 @@ class TestLockHandler(WebHooksTestcase):
             lockdir=ldir,
         )
         with self.assertRaises(LockdirNotExistsError) as cm:
-            locker.create_lockfile(self.lock_basename)
+            lock = locker.create_lockfile(self.lock_basename)
+            lock = None
         e = cm.exception
         LOG.debug(
             "%s raised as expected on lockdir = %r: %s.",
@@ -224,7 +228,8 @@ class TestLockHandler(WebHooksTestcase):
                 lockdir=ldir,
             )
             with self.assertRaises(LockdirNotWriteableError) as cm:
-                locker.create_lockfile(self.lock_basename)
+                lock = locker.create_lockfile(self.lock_basename)
+                lock = None
             e = cm.exception
             LOG.debug(
                 "%s raised as expected on lockdir = %r: %s.",
@@ -264,6 +269,7 @@ class TestLockHandler(WebHooksTestcase):
             self.assertEqual(lockfile, e.lockfile)
             if result:
                 self.fail("PbLockHandler shouldn't be able to create the lockfile.")
+                result = None
         finally:
             self.remove_lockfile(lockfile)
 
@@ -297,6 +303,7 @@ class TestLockHandler(WebHooksTestcase):
             LOG.debug(
                 "%s raised as expected on an invalid lockfile (empty lines): %s",
                 e.__class__.__name__, e)
+            result = None
 
         finally:
             self.remove_lockfile(lockfile)
@@ -335,6 +342,7 @@ class TestLockHandler(WebHooksTestcase):
             locker.remove_lockfile(lockfile)
             if result:
                 self.fail("LockHandler should not be able to create the lockfile.")
+                result = None
 
         finally:
             self.remove_lockfile(lockfile)
@@ -366,6 +374,7 @@ class TestLockHandler(WebHooksTestcase):
             locker.remove_lockfile(lockfile)
             if not result:
                 self.fail("PbLockHandler should be able to create the lockfile.")
+            result = None
         finally:
             self.remove_lockfile(lockfile)