1
0
Fork 0

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:
Jaroslav Škarvada 2016-06-07 17:58:45 +02:00
parent a174592cfd
commit b117e9204b
4 changed files with 215 additions and 144 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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