tuned-adm: rewrote multithreading code to glib
The glib/dbus doesn't like multithreading which is not handled by them, thus the code may segfault. According to https://bugzilla.redhat.com/show_bug.cgi?id=1330127#c2 it is not supportd and dbus-glib/dbus-python are not thread safe. This commit moves multithreading under glib and handles all in the glib mainloop, thus it may no longer segfaults. It also adds --timeout, -t command line parameter which may specify timeout for the sync operations, e.g.: # tuned-adm --timeout 200 profile balanced wil use 200 seconds timeout when waiting for the profile to load. The default timeout was increased to 90 seconds to match systemd default timeouts. Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
This commit is contained in:
parent
a174592cfd
commit
b117e9204b
4 changed files with 215 additions and 144 deletions
27
tuned-adm.py
27
tuned-adm.py
|
|
@ -27,12 +27,23 @@ import tuned.consts as consts
|
|||
import tuned.version as ver
|
||||
from tuned.utils.global_config import GlobalConfig
|
||||
|
||||
def check_positive(value):
|
||||
try:
|
||||
val = int(value)
|
||||
except ValueError:
|
||||
val = -1
|
||||
if val <= 0:
|
||||
raise argparse.ArgumentTypeError("%s has to be >= 0" % value)
|
||||
return val
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = GlobalConfig()
|
||||
parser = argparse.ArgumentParser(description="Manage tuned daemon.")
|
||||
parser.add_argument('--version', "-v", action = "version", version = "%%(prog)s %s.%s.%s" % (ver.TUNED_VERSION_MAJOR, ver.TUNED_VERSION_MINOR, ver.TUNED_VERSION_PATCH))
|
||||
parser.add_argument("--debug", "-d", action="store_true", help="show debug messages")
|
||||
parser.add_argument("--async", "-a", action="store_true", help="with dbus do not wait on commands completion and return immediately")
|
||||
parser.add_argument("--timeout", "-t", default = consts.ADMIN_TIMEOUT, type = check_positive, help="with sync operation use specific timeout instead of the default %d second(s)" % consts.ADMIN_TIMEOUT)
|
||||
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
parser_list = subparsers.add_parser("list", help="list available profiles")
|
||||
|
|
@ -65,20 +76,18 @@ if __name__ == "__main__":
|
|||
options = vars(args)
|
||||
debug = options.pop("debug")
|
||||
async = options.pop("async")
|
||||
timeout = options.pop("timeout")
|
||||
action_name = options.pop("action")
|
||||
result = False
|
||||
|
||||
if config.get(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON):
|
||||
dbus = True
|
||||
else:
|
||||
dbus = False
|
||||
try:
|
||||
if config.get(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON):
|
||||
controller = tuned.admin.DBusController(consts.DBUS_BUS, consts.DBUS_OBJECT, consts.DBUS_INTERFACE, debug)
|
||||
else:
|
||||
controller = None
|
||||
admin = tuned.admin.Admin(controller, debug, async)
|
||||
admin = tuned.admin.Admin(dbus, debug, async, timeout)
|
||||
|
||||
action = getattr(admin, action_name)
|
||||
result = action(**options)
|
||||
if controller is not None:
|
||||
controller.exit()
|
||||
result = admin.action(action_name, **options)
|
||||
except tuned.admin.TunedAdminException as e:
|
||||
if not debug:
|
||||
print >>sys.stderr, e
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import tuned.admin
|
||||
from tuned.utils.commands import commands
|
||||
from tuned.profiles import Locator as profiles_locator
|
||||
from exceptions import TunedAdminDBusException
|
||||
|
|
@ -5,23 +6,24 @@ import tuned.consts as consts
|
|||
import os
|
||||
import sys
|
||||
import errno
|
||||
import time
|
||||
import threading
|
||||
|
||||
class Admin(object):
|
||||
def __init__(self, controller, debug = False, async = False):
|
||||
self._controller = controller
|
||||
def __init__(self, dbus = True, debug = False, async = False, timeout = consts.ADMIN_TIMEOUT):
|
||||
self._dbus = dbus
|
||||
self._debug = debug
|
||||
self._async = async
|
||||
self._timeout = timeout
|
||||
self._cmd = commands(debug)
|
||||
self._profiles_locator = profiles_locator(consts.LOAD_DIRECTORIES)
|
||||
self._daemon_action_finished = threading.Event()
|
||||
self._daemon_action_profile = ""
|
||||
self._daemon_action_result = True
|
||||
self._daemon_action_errstr = ""
|
||||
if self._controller is None:
|
||||
self._dbus = False
|
||||
else:
|
||||
self._dbus = True
|
||||
self._controller = None
|
||||
if self._dbus:
|
||||
self._controller = tuned.admin.DBusController(consts.DBUS_BUS, consts.DBUS_OBJECT, consts.DBUS_INTERFACE, debug)
|
||||
try:
|
||||
self._controller.set_signal_handler(consts.DBUS_SIGNAL_PROFILE_CHANGED, self._signal_profile_changed_cb)
|
||||
except TunedAdminDBusException as e:
|
||||
|
|
@ -46,54 +48,74 @@ class Admin(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
def list(self):
|
||||
# run the action specified by the action_name with args
|
||||
def action(self, action_name, *args, **kwargs):
|
||||
if action_name is None or action_name == "":
|
||||
return False
|
||||
action = None
|
||||
action_dbus = None
|
||||
res = False
|
||||
try:
|
||||
action_dbus = getattr(self, "_action_dbus_" + action_name)
|
||||
except AttributeError as e:
|
||||
self._dbus = False
|
||||
try:
|
||||
action = getattr(self, "_action_" + action_name)
|
||||
except AttributeError as e:
|
||||
if not self._dbus:
|
||||
self._error(e + ", action '%s' is not implemented" % action_name)
|
||||
return False
|
||||
if self._dbus:
|
||||
try:
|
||||
profile_names = self._controller.profiles2()
|
||||
self._controller.set_action(action_dbus, *args, **kwargs)
|
||||
res = self._controller.run()
|
||||
except TunedAdminDBusException as e:
|
||||
# fallback to older API
|
||||
try:
|
||||
profile_names = self._controller.profiles()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
self._dbus = False
|
||||
profile_names = map(lambda profile:(profile, ""), profile_names)
|
||||
self._error(e)
|
||||
self._dbus = False
|
||||
|
||||
if not self._dbus:
|
||||
profile_names = self._profiles_locator.get_known_names_summary()
|
||||
res = action(*args, **kwargs)
|
||||
return res
|
||||
|
||||
def _print_profiles(self, profile_names):
|
||||
print "Available profiles:"
|
||||
for profile in profile_names:
|
||||
if profile[1] is not None and profile[1] != "":
|
||||
print self._cmd.align_str("- %s" % profile[0], 30, "- %s" % profile[1])
|
||||
else:
|
||||
print "- %s" % profile[0]
|
||||
self.active()
|
||||
|
||||
def _action_dbus_list(self):
|
||||
try:
|
||||
profile_names = self._controller.profiles2()
|
||||
except TunedAdminDBusException as e:
|
||||
# fallback to older API
|
||||
profile_names = map(lambda profile:(profile, ""), self._controller.profiles())
|
||||
self._print_profiles(profile_names)
|
||||
self._action_dbus_active()
|
||||
return self._controller.exit(True)
|
||||
|
||||
def _action_list(self):
|
||||
self._print_profiles(self._profiles_locator.get_known_names_summary())
|
||||
self._action_dbus_active()
|
||||
return True
|
||||
|
||||
def _dbus_get_active_profile(self):
|
||||
profile_name = self._controller.active_profile()
|
||||
if profile_name == "":
|
||||
profile_name = None
|
||||
self._controller.exit(True)
|
||||
return profile_name
|
||||
|
||||
def _get_active_profile(self):
|
||||
profile_name = None
|
||||
if self._dbus:
|
||||
try:
|
||||
profile_name = self._controller.active_profile()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
self._dbus = False
|
||||
if not self._dbus:
|
||||
profile_name = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE, None))
|
||||
profile_name = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE, None))
|
||||
if profile_name == "":
|
||||
profile_name = None
|
||||
return profile_name
|
||||
|
||||
def profile_info(self, profile = ""):
|
||||
if profile == "":
|
||||
profile = self._get_active_profile()
|
||||
if self._dbus:
|
||||
try:
|
||||
ret = self._controller.profile_info(profile)
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
self._dbus = False
|
||||
if not self._dbus:
|
||||
ret = self._profiles_locator.get_profile_attrs(profile, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], ["", ""])
|
||||
if ret[0] == True:
|
||||
def _print_profile_info(self, profile_info):
|
||||
if profile_info[0] == True:
|
||||
print "Profile name:"
|
||||
print ret[1]
|
||||
print
|
||||
|
|
@ -107,95 +129,103 @@ class Admin(object):
|
|||
print "Unable to get information about profile '%s'" % profile
|
||||
return False
|
||||
|
||||
def active(self):
|
||||
profile_name = self._get_active_profile()
|
||||
if profile_name is not None:
|
||||
if self._controller is not None and self._tuned_is_running():
|
||||
print "Current active profile: %s" % profile_name
|
||||
else:
|
||||
if self._controller is not None:
|
||||
print "It seems that tuned daemon is not running, preset profile is not activated."
|
||||
print "Preset profile: %s" % profile_name
|
||||
return True
|
||||
else:
|
||||
def _action_dbus_profile_info(self, profile = ""):
|
||||
if profile == "":
|
||||
profile = self._dbus_get_active_profile()
|
||||
return self._controller.exit(self._print_profile_info(self._controller.profile_info(profile)))
|
||||
|
||||
def _action_profile_info(self, profile = ""):
|
||||
if profile == "":
|
||||
profile = self._get_active_profile()
|
||||
return self._print_profile_info(self._profiles_locator.get_profile_attrs(profile, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], ["", ""]))
|
||||
|
||||
def _print_profile_name(self, profile_name):
|
||||
if profile_name is None:
|
||||
print "No current active profile."
|
||||
return False
|
||||
else:
|
||||
print "Current active profile: %s" % profile_name
|
||||
return True
|
||||
|
||||
def profile(self, profiles):
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
if self._dbus:
|
||||
self._daemon_action_finished.clear()
|
||||
try:
|
||||
(ret, msg) = self._controller.switch_profile(profile_name)
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
self._dbus = False
|
||||
if self._dbus and not self._async:
|
||||
waiting = True
|
||||
while waiting:
|
||||
if self._daemon_action_finished.wait(consts.ADMIN_TIMEOUT):
|
||||
if self._daemon_action_profile == profile_name:
|
||||
waiting = False
|
||||
if not self._daemon_action_result:
|
||||
print "Error changing profile: %s" % self._daemon_action_errstr
|
||||
return False
|
||||
else:
|
||||
print "Operation timed out"
|
||||
return False
|
||||
if not self._dbus:
|
||||
if profile_name in self._profiles_locator.get_known_names():
|
||||
if self._cmd.write_to_file(consts.ACTIVE_PROFILE_FILE, profile_name):
|
||||
print "Trying to (re)start tuned..."
|
||||
(ret, out) = self._cmd.execute(["service", "tuned", "restart"])
|
||||
if ret == 0:
|
||||
print "Tuned (re)started, changes applied."
|
||||
else:
|
||||
print "Tuned (re)start failed, you need to (re)start tuned by hand for changes to apply."
|
||||
return True
|
||||
else:
|
||||
self._error("Unable to switch profile, do you have enough permissions?")
|
||||
return False
|
||||
else:
|
||||
self._error("Requested profile '%s' doesn't exist." % profile_name)
|
||||
return False
|
||||
def _action_dbus_active(self):
|
||||
return self._controller.exit(self._print_profile_name(self._dbus_get_active_profile()))
|
||||
|
||||
def _action_active(self):
|
||||
profile_name = self._get_active_profile()
|
||||
if profile_name is not None and not self._tuned_is_running():
|
||||
print "It seems that tuned daemon is not running, preset profile is not activated."
|
||||
print "Preset profile: %s" % profile_name
|
||||
return True
|
||||
return self._print_profile_name(profile_name)
|
||||
|
||||
def _profile_print_status(self, ret, msg):
|
||||
if ret:
|
||||
if not self._controller.is_running() and not self._controller.start():
|
||||
self._error("Cannot enable the tuning.")
|
||||
ret = False
|
||||
else:
|
||||
self._error(msg)
|
||||
|
||||
return ret
|
||||
|
||||
def recommend_profile(self):
|
||||
if self._dbus:
|
||||
try:
|
||||
profile = self._controller.recommend_profile()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
self._dbus = False
|
||||
if not self._dbus:
|
||||
profile = self._cmd.recommend_profile()
|
||||
print profile
|
||||
def _action_dbus_wait_profile(self, profile_name):
|
||||
if time.time() >= self._timestamp + self._timeout:
|
||||
print "Operation timed out after waiting %d seconds(s), you may try to increase timeout by using --timeout command line option or using --async." % self._timeout
|
||||
return self._controller.exit(False)
|
||||
if self._daemon_action_finished.isSet():
|
||||
if self._daemon_action_profile == profile_name:
|
||||
if not self._daemon_action_result:
|
||||
print "Error changing profile: %s" % self._daemon_action_errstr
|
||||
return self._controller.exit(False)
|
||||
return self._controller.exit(True)
|
||||
return False
|
||||
|
||||
def verify_profile(self, ignore_missing):
|
||||
ret = False
|
||||
if self._controller is None:
|
||||
print "Not supported in no_daemon mode."
|
||||
def _action_dbus_profile(self, profiles):
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
self._daemon_action_finished.clear()
|
||||
(ret, msg) = self._controller.switch_profile(profile_name)
|
||||
if self._async:
|
||||
return self._controller.exit(self._profile_print_status(ret, msg))
|
||||
else:
|
||||
try:
|
||||
if ignore_missing:
|
||||
ret = self._controller.verify_profile_ignore_missing()
|
||||
else:
|
||||
ret = self._controller.verify_profile()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
self._error("Cannot verify profile if there is no compatible running Tuned daemon (or Tuned daemon is too old).")
|
||||
return False
|
||||
self._timestamp = time.time()
|
||||
self._controller.set_action(self._action_dbus_wait_profile, profile_name)
|
||||
return self._profile_print_status(ret, msg)
|
||||
|
||||
def _action_profile(self, profiles):
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
if profile_name in self._profiles_locator.get_known_names():
|
||||
if self._cmd.write_to_file(consts.ACTIVE_PROFILE_FILE, profile_name):
|
||||
print "Trying to (re)start tuned..."
|
||||
(ret, msg) = self._cmd.execute(["service", "tuned", "restart"])
|
||||
if ret == 0:
|
||||
print "Tuned (re)started, changes applied."
|
||||
else:
|
||||
print "Tuned (re)start failed, you need to (re)start tuned by hand for changes to apply."
|
||||
return True
|
||||
else:
|
||||
self._error("Unable to switch profile, do you have enough permissions?")
|
||||
return False
|
||||
else:
|
||||
self._error("Requested profile '%s' doesn't exist." % profile_name)
|
||||
return False
|
||||
return self._profile_print_status(self, ret, msg)
|
||||
|
||||
def _action_dbus_recommend_profile(self):
|
||||
print self._controller.recommend_profile()
|
||||
return self._controller.exit(True)
|
||||
|
||||
def _action_recommend_profile(self):
|
||||
print self._cmd.recommend_profile()
|
||||
return True
|
||||
|
||||
def _action_dbus_verify_profile(self, ignore_missing):
|
||||
if ignore_missing:
|
||||
ret = self._controller.verify_profile_ignore_missing()
|
||||
else:
|
||||
ret = self._controller.verify_profile()
|
||||
if ret:
|
||||
print "Verfication succeeded, current system settings match the preset profile."
|
||||
else:
|
||||
|
|
@ -203,17 +233,18 @@ class Admin(object):
|
|||
print "You can mostly fix this by Tuned restart, e.g.:"
|
||||
print " service tuned restart"
|
||||
print "See tuned log file ('%s') for details." % consts.LOG_FILE
|
||||
return ret
|
||||
return self._controller.exit(ret)
|
||||
|
||||
def off(self):
|
||||
if self._controller is None:
|
||||
print "Not supported in no_daemon mode."
|
||||
return False
|
||||
try:
|
||||
result = self._controller.off()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
return False
|
||||
if not result:
|
||||
def _action_verify_profile(self, ignore_missing):
|
||||
print "Not supported in no_daemon mode."
|
||||
return False
|
||||
|
||||
def _action_dbus_off(self):
|
||||
ret = self._controller.off()
|
||||
if not ret:
|
||||
self._error("Cannot disable active profile.")
|
||||
return result
|
||||
return self._controller.exit(ret)
|
||||
|
||||
def _action_off(self):
|
||||
print "Not supported in no_daemon mode."
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import threading
|
||||
import dbus
|
||||
import dbus.exceptions
|
||||
import time
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GLib, GObject
|
||||
from exceptions import TunedAdminDBusException
|
||||
|
||||
__all__ = ["DBusController"]
|
||||
|
|
@ -15,22 +15,53 @@ class DBusController(object):
|
|||
self._proxy = None
|
||||
self._debug = debug
|
||||
self._main_loop = None
|
||||
self._thread = None
|
||||
self._action = None
|
||||
self._ret = True
|
||||
self._exit = False
|
||||
self._exception = None
|
||||
|
||||
def _init_proxy(self):
|
||||
try:
|
||||
if self._proxy is None:
|
||||
# Probably not needed for PyGObject 3.10.2+
|
||||
GObject.threads_init()
|
||||
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
self._main_loop = GLib.MainLoop()
|
||||
self._thread = threading.Thread(target=self._thread_code)
|
||||
self._thread.start()
|
||||
bus = dbus.SystemBus()
|
||||
self._proxy = bus.get_object(self._bus_name, self._interface_name, self._object_name)
|
||||
except dbus.exceptions.DBusException:
|
||||
raise TunedAdminDBusException("Cannot talk to Tuned daemon via DBus. Is Tuned daemon running?")
|
||||
|
||||
def _thread_code(self):
|
||||
def _idle(self):
|
||||
if self._action is not None:
|
||||
# This may (and very probably will) run in child thread, so catch and pass exceptions to the main thread
|
||||
try:
|
||||
self._action_exit_code = self._action(*self._action_args, **self._action_kwargs)
|
||||
except TunedAdminDBusException as e:
|
||||
self._exception = e
|
||||
self._exit = True
|
||||
|
||||
if self._exit:
|
||||
self._main_loop.quit()
|
||||
return False
|
||||
else:
|
||||
time.sleep(1)
|
||||
return True
|
||||
|
||||
def set_action(self, action, *args, **kwargs):
|
||||
self._action = action
|
||||
self._action_args = args
|
||||
self._action_kwargs = kwargs
|
||||
|
||||
def run(self):
|
||||
self._exception = None
|
||||
GLib.idle_add(self._idle)
|
||||
self._main_loop.run()
|
||||
# Pass exception happened in child thread to the caller
|
||||
if self._exception is not None:
|
||||
raise self._exception
|
||||
return self._ret
|
||||
|
||||
def _call(self, method_name, *args, **kwargs):
|
||||
self._init_proxy()
|
||||
|
|
@ -86,8 +117,8 @@ class DBusController(object):
|
|||
def off(self):
|
||||
return self._call("disable")
|
||||
|
||||
def exit(self):
|
||||
if self._thread is not None and self._thread.is_alive():
|
||||
self._main_loop.quit()
|
||||
self._thread.join()
|
||||
self._thread = None
|
||||
def exit(self, ret):
|
||||
self.set_action(None)
|
||||
self._ret = ret
|
||||
self._exit = True
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -75,4 +75,4 @@ STR_VERIFY_PROFILE_VALUE_FAIL = "verify: failed: %s = %s, expected %s"
|
|||
STR_VERIFY_PROFILE_FAIL = "verify: failed: %s"
|
||||
|
||||
# timout for tuned-adm operations in seconds
|
||||
ADMIN_TIMEOUT = 60
|
||||
ADMIN_TIMEOUT = 90
|
||||
|
|
|
|||
Loading…
Reference in a new issue