Loading tools/edit_monitor/daemon_manager.py +37 −19 Original line number Diff line number Diff line Loading @@ -13,6 +13,8 @@ # limitations under the License. import errno import fcntl import getpass import hashlib import logging Loading Loading @@ -100,16 +102,32 @@ class DaemonManager: logging.warning("Edit monitor for cog is not supported, exiting...") return setup_lock_file = pathlib.Path(tempfile.gettempdir()).joinpath( self.pid_file_path.name + ".setup" ) logging.info("setup lock file: %s", setup_lock_file) with open(setup_lock_file, "w") as f: try: # Acquire an exclusive lock fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) self._stop_any_existing_instance() self._write_pid_to_pidfile() self._start_daemon_process() except Exception as e: if ( isinstance(e, IOError) and e.errno == errno.EAGAIN ): # Failed to acquire the file lock. logging.warning("Another edit monitor is starting, exitinng...") return else: logging.exception("Failed to start daemon manager with error %s", e) self._send_error_event_to_clearcut( edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR ) raise e finally: # Release the lock fcntl.flock(f, fcntl.LOCK_UN) def monitor_daemon( self, Loading Loading @@ -414,18 +432,18 @@ class DaemonManager: pids = [] try: output = subprocess.check_output( ["ps", "-ef", "--no-headers"], text=True) output = subprocess.check_output(["ps", "-ef", "--no-headers"], text=True) for line in output.splitlines(): parts = line.split() process_path = parts[7] if pathlib.Path(process_path).name == 'edit_monitor': if pathlib.Path(process_path).name == "edit_monitor": pid = int(parts[1]) if pid != self.pid: # exclude the current process pids.append(pid) except Exception: logging.exception( "Failed to get pids of existing edit monitors from ps command.") "Failed to get pids of existing edit monitors from ps command." ) return pids Loading tools/edit_monitor/daemon_manager_test.py +30 −5 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ """Unittests for DaemonManager.""" import fcntl import logging import multiprocessing import os Loading Loading @@ -82,7 +83,8 @@ class DaemonManagerTest(unittest.TestCase): # tests will be cleaned. tempfile.tempdir = self.working_dir.name self.patch = mock.patch.dict( os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'true'}) os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'true'} ) self.patch.start() def tearDown(self): Loading @@ -102,6 +104,7 @@ class DaemonManagerTest(unittest.TestCase): p = self._create_fake_deamon_process() self.assert_run_simple_daemon_success() self.assert_no_subprocess_running() def test_start_success_with_existing_instance_already_dead(self): # Create a pidfile with pid that does not exist. Loading Loading @@ -137,7 +140,9 @@ class DaemonManagerTest(unittest.TestCase): # Verify no daemon process is started. self.assertIsNone(dm.daemon_process) @mock.patch.dict(os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'false'}, clear=True) @mock.patch.dict( os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'false'}, clear=True ) def test_start_return_directly_if_disabled(self): dm = daemon_manager.DaemonManager(TEST_BINARY_FILE) dm.start() Loading @@ -154,6 +159,25 @@ class DaemonManagerTest(unittest.TestCase): # Verify no daemon process is started. self.assertIsNone(dm.daemon_process) def test_start_failed_other_instance_is_starting(self): f = open( pathlib.Path(self.working_dir.name).joinpath( TEST_PID_FILE_PATH + '.setup' ), 'w', ) # Acquire an exclusive lock fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) dm = daemon_manager.DaemonManager(TEST_BINARY_FILE) dm.start() # Release the lock fcntl.flock(f, fcntl.LOCK_UN) f.close() # Verify no daemon process is started. self.assertIsNone(dm.daemon_process) @mock.patch('os.kill') def test_start_failed_to_kill_existing_instance(self, mock_kill): mock_kill.side_effect = OSError('Unknown OSError') Loading @@ -177,6 +201,7 @@ class DaemonManagerTest(unittest.TestCase): 'edit_monitor' ) pid_file_path_dir.mkdir(parents=True, exist_ok=True) # Makes the directory read-only so write pidfile will fail. os.chmod(pid_file_path_dir, 0o555) Loading Loading @@ -452,7 +477,7 @@ class DaemonManagerTest(unittest.TestCase): pass def _create_fake_deamon_process( self, name: str = '' self, name: str = TEST_PID_FILE_PATH ) -> multiprocessing.Process: # Create a long running subprocess p = multiprocessing.Process(target=long_running_daemon) Loading @@ -463,7 +488,7 @@ class DaemonManagerTest(unittest.TestCase): 'edit_monitor' ) pid_file_path_dir.mkdir(parents=True, exist_ok=True) with open(pid_file_path_dir.joinpath(name + 'pid.lock'), 'w') as f: with open(pid_file_path_dir.joinpath(name), 'w') as f: f.write(str(p.pid)) return p Loading Loading
tools/edit_monitor/daemon_manager.py +37 −19 Original line number Diff line number Diff line Loading @@ -13,6 +13,8 @@ # limitations under the License. import errno import fcntl import getpass import hashlib import logging Loading Loading @@ -100,16 +102,32 @@ class DaemonManager: logging.warning("Edit monitor for cog is not supported, exiting...") return setup_lock_file = pathlib.Path(tempfile.gettempdir()).joinpath( self.pid_file_path.name + ".setup" ) logging.info("setup lock file: %s", setup_lock_file) with open(setup_lock_file, "w") as f: try: # Acquire an exclusive lock fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) self._stop_any_existing_instance() self._write_pid_to_pidfile() self._start_daemon_process() except Exception as e: if ( isinstance(e, IOError) and e.errno == errno.EAGAIN ): # Failed to acquire the file lock. logging.warning("Another edit monitor is starting, exitinng...") return else: logging.exception("Failed to start daemon manager with error %s", e) self._send_error_event_to_clearcut( edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR ) raise e finally: # Release the lock fcntl.flock(f, fcntl.LOCK_UN) def monitor_daemon( self, Loading Loading @@ -414,18 +432,18 @@ class DaemonManager: pids = [] try: output = subprocess.check_output( ["ps", "-ef", "--no-headers"], text=True) output = subprocess.check_output(["ps", "-ef", "--no-headers"], text=True) for line in output.splitlines(): parts = line.split() process_path = parts[7] if pathlib.Path(process_path).name == 'edit_monitor': if pathlib.Path(process_path).name == "edit_monitor": pid = int(parts[1]) if pid != self.pid: # exclude the current process pids.append(pid) except Exception: logging.exception( "Failed to get pids of existing edit monitors from ps command.") "Failed to get pids of existing edit monitors from ps command." ) return pids Loading
tools/edit_monitor/daemon_manager_test.py +30 −5 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ """Unittests for DaemonManager.""" import fcntl import logging import multiprocessing import os Loading Loading @@ -82,7 +83,8 @@ class DaemonManagerTest(unittest.TestCase): # tests will be cleaned. tempfile.tempdir = self.working_dir.name self.patch = mock.patch.dict( os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'true'}) os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'true'} ) self.patch.start() def tearDown(self): Loading @@ -102,6 +104,7 @@ class DaemonManagerTest(unittest.TestCase): p = self._create_fake_deamon_process() self.assert_run_simple_daemon_success() self.assert_no_subprocess_running() def test_start_success_with_existing_instance_already_dead(self): # Create a pidfile with pid that does not exist. Loading Loading @@ -137,7 +140,9 @@ class DaemonManagerTest(unittest.TestCase): # Verify no daemon process is started. self.assertIsNone(dm.daemon_process) @mock.patch.dict(os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'false'}, clear=True) @mock.patch.dict( os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'false'}, clear=True ) def test_start_return_directly_if_disabled(self): dm = daemon_manager.DaemonManager(TEST_BINARY_FILE) dm.start() Loading @@ -154,6 +159,25 @@ class DaemonManagerTest(unittest.TestCase): # Verify no daemon process is started. self.assertIsNone(dm.daemon_process) def test_start_failed_other_instance_is_starting(self): f = open( pathlib.Path(self.working_dir.name).joinpath( TEST_PID_FILE_PATH + '.setup' ), 'w', ) # Acquire an exclusive lock fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) dm = daemon_manager.DaemonManager(TEST_BINARY_FILE) dm.start() # Release the lock fcntl.flock(f, fcntl.LOCK_UN) f.close() # Verify no daemon process is started. self.assertIsNone(dm.daemon_process) @mock.patch('os.kill') def test_start_failed_to_kill_existing_instance(self, mock_kill): mock_kill.side_effect = OSError('Unknown OSError') Loading @@ -177,6 +201,7 @@ class DaemonManagerTest(unittest.TestCase): 'edit_monitor' ) pid_file_path_dir.mkdir(parents=True, exist_ok=True) # Makes the directory read-only so write pidfile will fail. os.chmod(pid_file_path_dir, 0o555) Loading Loading @@ -452,7 +477,7 @@ class DaemonManagerTest(unittest.TestCase): pass def _create_fake_deamon_process( self, name: str = '' self, name: str = TEST_PID_FILE_PATH ) -> multiprocessing.Process: # Create a long running subprocess p = multiprocessing.Process(target=long_running_daemon) Loading @@ -463,7 +488,7 @@ class DaemonManagerTest(unittest.TestCase): 'edit_monitor' ) pid_file_path_dir.mkdir(parents=True, exist_ok=True) with open(pid_file_path_dir.joinpath(name + 'pid.lock'), 'w') as f: with open(pid_file_path_dir.joinpath(name), 'w') as f: f.write(str(p.pid)) return p Loading