From 7c2c9437bb09b4e072319a850d47bea561b70820 Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Wed, 15 Feb 2012 17:23:32 +0100 Subject: [PATCH] [entropy.misc] add FlockFile class The purpose of this class is to implement a file-based lock, abstracting away from fcntl. --- lib/entropy/misc.py | 79 +++++++++++++++++++++++++++++++++++++++++++++ lib/tests/misc.py | 20 +++++++++++- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/lib/entropy/misc.py b/lib/entropy/misc.py index adf6884db..00a38254a 100644 --- a/lib/entropy/misc.py +++ b/lib/entropy/misc.py @@ -15,6 +15,8 @@ import os import sys import time +import fcntl +import errno import codecs if sys.hexversion >= 0x3000000: import urllib.request, urllib.error, urllib.parse @@ -26,8 +28,10 @@ else: import logging import threading from collections import deque + from entropy.const import etpConst, const_isunicode, \ const_isfileobj, const_convert_log_level, const_setup_file +from entropy.exceptions import EntropyException import entropy.tools @@ -540,6 +544,81 @@ class MasterSlaveLock(object): self.__master_locked = False +class FlockFile(object): + + """ + Of flock() operations on a file. + """ + + class FlockFileInitFailure(EntropyException): + """ + FlockFile initialization failure exception. + Can be raised either because file path does + not exist (missing directory) or permissions + are not sufficient. + """ + + def __init__(self, file_path, fd = None): + self._path = file_path + if fd is None: + try: + self._f = open(self._path, "a+") + except IOError as err: + if err.errno in (errno.ENOENT, errno.EACCES): + raise FlockFileInitFailure(err) + raise + else: + self._f = os.fdopen(fd) + + def acquire_shared(self): + """ + Acquire the lock in shared mode. + """ + flags = fcntl.LOCK_SH + try: + fcntl.flock(self._f.fileno(), flags) + except IOError as err: + self.close() + raise + + def acquire_exclusive(self): + """ + Acquire the lock in exclusive mode. + """ + flags = fcntl.LOCK_EX + try: + fcntl.flock(self._f.fileno(), flags) + except IOError as err: + self.close() + raise + + def promote(self): + """ + Promote a lock acquired in shared mode to exclusive mode. + """ + self.acquire_shared() + self.acquire_exclusive() + + def demote(self): + """ + Demote a lock acquired in exclusive mode to shared mode. + """ + self.release() + self.acquire_shared() + + def release(self): + """ + Release the lock previously acquired. + """ + fcntl.flock(self._f.fileno(), fcntl.LOCK_UN) + + def close(self): + """ + Close the underlying file object. + """ + self._f.close() + + class EmailSender: """ diff --git a/lib/tests/misc.py b/lib/tests/misc.py index a46d6f3e0..0d9a523af 100644 --- a/lib/tests/misc.py +++ b/lib/tests/misc.py @@ -8,7 +8,7 @@ import tempfile import json from entropy.const import const_convert_to_unicode from entropy.misc import Lifo, TimeScheduled, ParallelTask, EmailSender, \ - FastRSS + FastRSS, FlockFile class MiscTest(unittest.TestCase): @@ -104,6 +104,24 @@ class MiscTest(unittest.TestCase): #print "joined" self.assertTrue(self.t_sched_run) + def test_flock_file(self): + tmp_fd, tmp_path = None, None + try: + tmp_fd, tmp_path = tempfile.mkstemp(prefix="entropy.misc.test") + mf = FlockFile(tmp_path, fd = tmp_fd) + mf.acquire_exclusive() + mf.demote() + mf.promote() + mf.release() + mf.acquire_shared() + mf.release() + mf.close() + finally: + if tmp_fd is not None: + self.assertRaises(OSError, os.close, tmp_fd) + if tmp_path is not None: + os.remove(tmp_path) + def test_email_sender(self): mail_sender = 'test@test.com'