Optimize pid property in AsyncProcess class

The pid property in the the AsyncProcess process class executes
command 'ps --ppid <pid> -o pid=' every time the associated
asynchronous process's pid is needed. This can be very heavy
in high load network and compute nodes. Please see related bug.

This change optimizes the pid property to execute 'ps --ppid <pid> -o
pid=' only the first time it is called, memoizing the retrieved pid for
future calls.

Change-Id: Idd347b0b22320cbfdb5d6a738e218e5788dd7d6b
Closes-Bug:1663465
This commit is contained in:
jufeng 2017-02-10 12:19:56 +08:00 committed by jesse
parent 44a51b8c09
commit ee5677bd61
2 changed files with 31 additions and 6 deletions

View File

@ -78,6 +78,7 @@ class AsyncProcess(object):
raise ValueError(_('respawn_interval must be >= 0 if provided.'))
self.respawn_interval = respawn_interval
self._process = None
self._pid = None
self._is_running = False
self._kill_event = None
self._reset_queues()
@ -95,8 +96,8 @@ class AsyncProcess(object):
def is_active(self):
# If using sudo rootwrap as a root_helper, we have to wait until sudo
# spawns rootwrap and rootwrap spawns the process.
# spawns rootwrap and rootwrap spawns the process. self.pid will make
# sure to get the correct pid.
return utils.pid_invoked_with_cmdline(
self.pid, self.cmd_without_namespace)
@ -137,6 +138,7 @@ class AsyncProcess(object):
def _spawn(self):
"""Spawn a process and its watchers."""
self._is_running = True
self._pid = None
self._kill_event = eventlet.event.Event()
self._process, cmd = utils.create_process(self._cmd,
run_as_root=self.run_as_root)
@ -154,16 +156,19 @@ class AsyncProcess(object):
@property
def pid(self):
if self._process:
return utils.get_root_helper_child_pid(
self._process.pid,
self.cmd_without_namespace,
run_as_root=self.run_as_root)
if not self._pid:
self._pid = utils.get_root_helper_child_pid(
self._process.pid,
self.cmd_without_namespace,
run_as_root=self.run_as_root)
return self._pid
def _kill(self, kill_signal):
"""Kill the process and the associated watcher greenthreads."""
pid = self.pid
if pid:
self._is_running = False
self._pid = None
self._kill_process(pid, kill_signal)
# Halt the greenthreads if they weren't already.

View File

@ -56,6 +56,25 @@ class TestAsyncProcess(base.BaseTestCase):
])
self.assertEqual(len(proc._watchers), 2)
def test__pid_none(self):
pid = 1
self.proc._pid = None
with mock.patch.object(self.proc, '_process') as _process:
with mock.patch.object(utils,
'get_root_helper_child_pid') as func:
func.return_value = pid
self.assertEqual(self.proc.pid, pid)
func.assert_called_once_with(_process.pid, ['fake'],
run_as_root=False)
self.assertEqual(self.proc._pid, pid)
def test__pid_not_none(self):
self.proc._pid = 1
with mock.patch.object(self.proc, '_process'),\
mock.patch.object(utils, 'get_root_helper_child_pid') as func:
self.assertEqual(self.proc.pid, 1)
func.assert_not_called()
def test__handle_process_error_kills_with_respawn(self):
with mock.patch.object(self.proc, '_kill') as kill:
self.proc._handle_process_error()
@ -185,6 +204,7 @@ class TestAsyncProcess(base.BaseTestCase):
self.assertIsNone(self.proc._kill_event)
self.assertFalse(self.proc._is_running)
self.assertIsNone(self.proc._pid)
mock_kill_event.send.assert_called_once_with()
if pid: