From 3895f164975cdb39818e243a1e13f2b40ce4490b Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Fri, 9 Mar 2018 16:55:03 +0100 Subject: [PATCH] Adding possibility to stay a lockfile opened. --- lib/webhooks/lock_handler.py | 75 ++++++++++++++++++++++++++++++++---- test/test_lock.py | 19 ++++++--- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/lib/webhooks/lock_handler.py b/lib/webhooks/lock_handler.py index 04b27d7..3a7e8bc 100644 --- a/lib/webhooks/lock_handler.py +++ b/lib/webhooks/lock_handler.py @@ -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) diff --git a/test/test_lock.py b/test/test_lock.py index 8d7fe83..76c87f7 100755 --- a/test/test_lock.py +++ b/test/test_lock.py @@ -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) -- 2.39.5