def run(self): self.mutex = LamportMutex(path=self.mutex_path, id=self.id, port=self.port, ids=self.ids, ports=self.ports, logger=self.logger) if self.stress_mode: self.run_stress_mode()
def setUp(self): self.mutex_path = '../mutex.txt' self.logger = Logger(off=True) self.other_ids = [i for i in range(1, 10)] self.other_ports = [8100 + i for i in range(1, 10)] self.mutex = LamportMutex(self.mutex_path, 0, 8000, self.other_ids, self.other_ports, self.logger) self.mutex.tear_down() # kill real connection # mock API mock_api = MockAPI(self.other_ids) self.mutex.api = mock_api
class TestMutex(TestCase): def setUp(self): self.mutex_path = '../mutex.txt' self.logger = Logger(off=True) self.other_ids = [i for i in range(1, 10)] self.other_ports = [8100 + i for i in range(1, 10)] self.mutex = LamportMutex(self.mutex_path, 0, 8000, self.other_ids, self.other_ports, self.logger) self.mutex.tear_down() # kill real connection # mock API mock_api = MockAPI(self.other_ids) self.mutex.api = mock_api def test__mutex_lock(self): # acquire mutex self.assertEqual(self.mutex.lock(), True) # if mutex was locked resource must be unavailable: with open(self.mutex_path, 'w') as f: with self.assertRaises(BlockingIOError): fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) self.mutex.unlock() # release mutex def test__mutex_unlock(self): # acquire mutex self.assertEqual(self.mutex.lock(), True) self.mutex.unlock() # release mutex # resource must be available: with open(self.mutex_path, 'w') as f: fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
def setUp(self): self.mutex_path = '../mutex.txt' self.logger = Logger(off=True) self.logger.warn('Ignore logger error during tests!') self.other_ids = [i for i in range(1, 10)] self.other_ports = [8100 + i for i in range(1, 10)] self.mutex = LamportMutex(self.mutex_path, 0, 8000, self.other_ids, self.other_ports, self.logger) self.mutex.tear_down() # kill real connection # mock connection self.mock_connection = MockConnection(0, 8000, self.other_ids, self.other_ports, self.mutex.api.on_response) self.mutex.api.connection = self.mock_connection
class TestAPI(TestCase): def setUp(self): self.mutex_path = '../mutex.txt' self.logger = Logger(off=True) self.logger.warn('Ignore logger error during tests!') self.other_ids = [i for i in range(1, 10)] self.other_ports = [8100 + i for i in range(1, 10)] self.mutex = LamportMutex(self.mutex_path, 0, 8000, self.other_ids, self.other_ports, self.logger) self.mutex.tear_down() # kill real connection # mock connection self.mock_connection = MockConnection(0, 8000, self.other_ids, self.other_ports, self.mutex.api.on_response) self.mutex.api.connection = self.mock_connection def test__API_with_correct_answers(self): """ mock connection works correctly """ self.mock_connection.state = MockConnection.CORRECT_NUM_OF_CONFIRMATIONS # mutex must be acquired self.assertEqual(self.mutex.lock(), True) # acquire mutex self.mutex.unlock() # release mutex def test__API_with_wrong_answer(self): """ mock connection send less confirmation than required """ self.mock_connection.state = MockConnection.WRONG_NUM_OF_CONFIRMATIONS # timeout supposed to be here self.assertEqual(self.mutex.lock(), False) # acquire mutex def test__API_without_answer(self): """ actually is the same that previous one """ self.mock_connection.state = MockConnection.NO_CONFIRMATIONS # timeout supposed to be here self.assertEqual(self.mutex.lock(), False) # acquire mutex def test__API_increment_clock(self): """ test whether clock increment works correctly """ self.mock_connection.state = MockConnection.CORRECT_NUM_OF_CONFIRMATIONS # simulate request other_time = 10 self.mock_connection.simulate_request_from_other_process(other_time) # time bigger than ours # add 1 after receiving # add one more 1 after sending confirmation self.assertEqual(self.mutex.api.clock, other_time + 2) # now out time is equal to other_time + 2 # let's receive 'release' message with older time than ours now: self.mock_connection.simulate_release_from_other_process(other_time + 1, 0) # our time is supposed to be equal to other_time + 3 now: # because we only increment it once when receive 'release' message self.assertEqual(self.mutex.api.clock, other_time + 3) def test__API_other_locks_earlier(self): """ check that we await until mutex is released before lock it """ # mock connection self. mock_connection.state = MockConnection.CORRECT_NUM_OF_CONFIRMATIONS # simulate request self.mock_connection.simulate_request_from_other_process(-1) # earlier than our time # simulate release after 1.5s delay = 2 thread = Thread(target=self.mock_connection.simulate_release_from_other_process, args=(2, delay)) thread.start() start = time() self.mutex.lock() # locked only after ~2s => OK self.assertGreaterEqual(time(), start + delay) self.mutex.unlock() def test__API_other_locks_later(self): """ lock mutex if our process is first in the queue """ self.mock_connection.state = MockConnection.CORRECT_NUM_OF_CONFIRMATIONS # simulate release after 1.5s delay = 1.5 thread = Thread(target=self.mock_connection.simulate_release_from_other_process, args=(1, delay)) thread.start() start = time() self.mutex.lock() # simulate request self.mock_connection.simulate_request_from_other_process(10) # later than our time # locked before delay passed=> OK self.assertLess(time(), start + delay) self.mutex.unlock()
class DaemonProcess(Daemon): mutex = None def __init__(self, pidfile, path=None, id=None, port=None, ids=None, ports=None, logger=None, debug=False, stress_mode=False): """ :param path: path to the mutex file. for example mutex.txt :param logger: instance of logger :param id: self id :param port: self port :param ids: ids of other processes :param ports: ports of other processes :param stress_mode: in stress mode process acquires and releases mutex in loop while alive """ self.id = id self.mutex_path = path self.port = port self.ids = ids self.ports = ports self.logger = logger self.stress_mode = stress_mode signal(SIGUSR1, self.__lock) signal(SIGUSR2, self.__unlock) super().__init__(pidfile, debug) def signal_term_handler(self, signum, frame): self.mutex.tear_down() super().signal_term_handler(signum, frame) def run(self): self.mutex = LamportMutex(path=self.mutex_path, id=self.id, port=self.port, ids=self.ids, ports=self.ports, logger=self.logger) if self.stress_mode: self.run_stress_mode() def run_stress_mode(self): self.logger.warn("{} wait other processes...".format(self.id)) while not self.mutex.api.ping_all(): continue self.logger.warn("{} run stress mode".format(self.id)) while 1: if self.__lock(None, None): self.__unlock(None, None) def get_pid(self): try: with open(self.pidfile, 'r') as pf: pid = int(pf.read().strip()) except IOError: pid = None return pid def lock(self): pid = self.get_pid() if not pid: message = "pidfile %s does not exist. Daemon not running?\n" sys.stderr.write(message % self.pidfile) return os.kill(pid, SIGUSR1) def unlock(self): pid = self.get_pid() if not pid: message = "pidfile %s does not exist. Daemon not running?\n" sys.stderr.write(message % self.pidfile) return os.kill(pid, SIGUSR2) def __lock(self, signum, frame): return self.mutex.lock() def __unlock(self, signum, frame): self.mutex.unlock()
parser.parse_arguments(sys.argv, logger) logger = Logger(debug=debug, out='{}/{}.log'.format(settings.logs_path, id)) if daemon: daemon = DaemonProcess(pidfile="{}/{}.pid".format( settings.pids_path, id), path=mutex_path, id=id, port=port, ids=ids, ports=ports, logger=logger, debug=debug, stress_mode=stress_mode) daemon.start() else: mutex = LamportMutex(path=mutex_path, id=id, port=port, ids=ids, ports=ports, logger=logger) sleep( 1) # sleep for a while because socket doesn't open immediately if stress_mode: run_stress_mode() else: run_interactive_app() except ValueError: exit(0)