mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
dd5d865020
The following operations would hang if lvm was compiled with 'enable-notify-dbus' and the client specified -1 for the timeout: * LV snapshot merge * VG move * LV move This was caused because the implementation of these three dbus methods is different. Most of the dbus method calls are executed by gathering information needed to fulfill it, placing that information on a thread safe queue and returning. The results later to be returned to the client with callbacks. With this approach we can process an arbitrary number of commands without any of them blocking other dbus commands. However, the 3 dbus methods listed above did not utilize this functionality because they were implemented with a separate thread that handles the fork & exec of lvm. This is done because these operations can be very slow to complete. However, because of this the lvm command that we were waiting on is trying to call back into the dbus service to notify it that something changed. Because the code was blocking the process that handles the incoming dbus activity the lvm command blocked. We were stuck until the client timed-out the connection, which then causes the service to unblock and continue. If the client did not have a timeout, we would have been hung indefinitely. The fix is to always utilize the worker queue on all dbus methods. We need to ensure that lvm is tested with 'enable-notify-dbus' enabled and disabled.
152 lines
4.0 KiB
Python
152 lines
4.0 KiB
Python
# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved.
|
|
#
|
|
# This copyrighted material is made available to anyone wishing to use,
|
|
# modify, copy, or redistribute it subject to the terms and conditions
|
|
# of the GNU General Public License v.2.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import threading
|
|
# noinspection PyUnresolvedReferences
|
|
from gi.repository import GLib
|
|
from .job import Job
|
|
from . import cfg
|
|
import traceback
|
|
from .utils import log_error
|
|
|
|
|
|
class RequestEntry(object):
|
|
def __init__(self, tmo, method, arguments, cb, cb_error,
|
|
return_tuple=True, job_state=None):
|
|
self.tmo = tmo
|
|
self.method = method
|
|
self.arguments = arguments
|
|
self.cb = cb
|
|
self.cb_error = cb_error
|
|
|
|
self.timer_id = -1
|
|
self.lock = threading.RLock()
|
|
self.done = False
|
|
self._result = None
|
|
self._job = None
|
|
self._rc = 0
|
|
self._rc_error = None
|
|
self._return_tuple = return_tuple
|
|
self._job_state = job_state
|
|
|
|
if self.tmo < 0:
|
|
# Client is willing to block forever
|
|
pass
|
|
elif tmo == 0:
|
|
self._return_job()
|
|
else:
|
|
self.timer_id = GLib.timeout_add_seconds(
|
|
tmo, RequestEntry._request_timeout, self)
|
|
|
|
@staticmethod
|
|
def _request_timeout(r):
|
|
"""
|
|
Method which gets called when the timer runs out!
|
|
:param r: RequestEntry which timed out
|
|
:return: Nothing
|
|
"""
|
|
r.timer_expired()
|
|
|
|
def _return_job(self):
|
|
self._job = Job(self, self._job_state)
|
|
cfg.om.register_object(self._job, True)
|
|
if self._return_tuple:
|
|
self.cb(('/', self._job.dbus_object_path()))
|
|
else:
|
|
self.cb(self._job.dbus_object_path())
|
|
|
|
def run_cmd(self):
|
|
try:
|
|
result = self.method(*self.arguments)
|
|
self.register_result(result)
|
|
except Exception as e:
|
|
# Use the request entry to return the result as the client may
|
|
# have gotten a job by the time we hit an error
|
|
# Lets get the stacktrace and set that to the error message
|
|
st = traceback.format_exc()
|
|
cfg.blackbox.dump()
|
|
log_error("Exception returned to client: \n%s" % st)
|
|
self.register_error(-1, str(e), e)
|
|
|
|
def is_done(self):
|
|
with self.lock:
|
|
rc = self.done
|
|
return rc
|
|
|
|
def get_errors(self):
|
|
with self.lock:
|
|
return (self._rc, self._rc_error)
|
|
|
|
def result(self):
|
|
with self.lock:
|
|
if self.done:
|
|
return self._result
|
|
return '/'
|
|
|
|
def _reg_ending(self, result, error_rc=0, error_msg=None,
|
|
error_exception=None):
|
|
with self.lock:
|
|
self.done = True
|
|
if self.timer_id != -1:
|
|
# Try to prevent the timer from firing
|
|
GLib.source_remove(self.timer_id)
|
|
|
|
self._result = result
|
|
self._rc = error_rc
|
|
self._rc_error = error_msg
|
|
|
|
if not self._job:
|
|
# We finished and there is no job, so return result or error
|
|
# now!
|
|
# Note: If we don't have a valid cb or cbe, this indicates a
|
|
# request that doesn't need a response as we already returned
|
|
# one before the request was processed.
|
|
if error_rc == 0:
|
|
if self.cb:
|
|
if self._return_tuple:
|
|
self.cb((result, '/'))
|
|
else:
|
|
self.cb(result)
|
|
else:
|
|
if self.cb_error:
|
|
if not error_exception:
|
|
if not error_msg:
|
|
error_exception = Exception(
|
|
"An error occurred, but no reason was "
|
|
"given, see service logs!")
|
|
else:
|
|
error_exception = Exception(error_msg)
|
|
|
|
self.cb_error(error_exception)
|
|
else:
|
|
# We have a job and it's complete, indicate that it's done.
|
|
# TODO: We need to signal the job is done too.
|
|
self._job.Complete = True
|
|
self._job = None
|
|
|
|
def register_error(self, error_rc, error_message, error_exception):
|
|
self._reg_ending(None, error_rc, error_message, error_exception)
|
|
|
|
def register_result(self, result):
|
|
self._reg_ending(result)
|
|
|
|
def timer_expired(self):
|
|
with self.lock:
|
|
# Set the timer back to -1 as we will get a warning if we try
|
|
# to remove a timer that doesn't exist
|
|
self.timer_id = -1
|
|
if not self.done:
|
|
# Create dbus job object and return path to caller
|
|
self._return_job()
|
|
else:
|
|
# The job is done, we have nothing to do
|
|
pass
|
|
|
|
return False
|