diff --git a/pkg/systemd/overview-cards/tuned-dialog.jsx b/pkg/systemd/overview-cards/tuned-dialog.jsx index cc7eb13272cf..dbbdb45cc4c0 100644 --- a/pkg/systemd/overview-cards/tuned-dialog.jsx +++ b/pkg/systemd/overview-cards/tuned-dialog.jsx @@ -18,7 +18,7 @@ */ import cockpit from "cockpit"; -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { Modal } from "@patternfly/react-core/dist/esm/components/Modal/index.js"; @@ -31,84 +31,89 @@ import { EmptyStatePanel } from 'cockpit-components-empty-state.jsx'; import { ModalError } from 'cockpit-components-inline-notification.jsx'; import { ProfilesMenuDialogBody } from './profiles-menu-dialog-body.jsx'; import { superuser } from 'superuser'; -import { useObject, useEvent } from "hooks"; +import { useEvent, useInit } from "hooks"; import { useDialogs } from "dialogs.jsx"; const _ = cockpit.gettext; +function poll_tuned_state(tuned, tunedService) { + return Promise.all([ + tuned.call('/Tuned', 'com.redhat.tuned.control', 'is_running', []), + tuned.call('/Tuned', 'com.redhat.tuned.control', 'active_profile', []), + tuned.call('/Tuned', 'com.redhat.tuned.control', 'recommend_profile', []) + ]) + .then(([[is_running], [active_result], [recommended]]) => { + const active = is_running ? active_result : "none"; + return ({ state: "running", active, recommended }); + }) + .catch((ex) => { + if (!tunedService.exists) + return ({ state: "not-installed" }); + else if (tunedService.state != "running") + return ({ state: "not-running" }); + else + return Promise.reject(ex); + }); +} + export const TunedPerformanceProfile = () => { const Dialogs = useDialogs(); const [btnText, setBtnText] = useState(); const [state, setState] = useState(); const [status, setStatus] = useState(); - - const tunedService = useObject(() => service.proxy("tuned.service"), - null, - []); - const tuned = useObject(() => cockpit.dbus("com.redhat.tuned", { superuser: "try" }), - obj => obj.close(), - [superuser.allowed, tunedService.state]); - - const poll = useCallback(() => { - return Promise.all([ - tuned.call('/Tuned', 'com.redhat.tuned.control', 'is_running', []), - tuned.call('/Tuned', 'com.redhat.tuned.control', 'active_profile', []), - tuned.call('/Tuned', 'com.redhat.tuned.control', 'recommend_profile', []) - ]) - .then(([[is_running], [active_result], [recommended]]) => { - const active = is_running ? active_result : "none"; - return ({ state: "running", active, recommended }); - }) - .catch((ex) => { - if (!tunedService.exists) - return ({ state: "not-installed" }); - else if (tunedService.state != "running") - return ({ state: "not-running" }); - else - return Promise.reject(ex); - }); - }, [tunedService, tuned]); - - const updateButton = useCallback(() => { - return poll() - .then(res => { - const { state, active, recommended } = res; - let status; - - if (state == "not-installed") - status = _("Tuned is not available"); - else if (state == "not-running") - status = _("Tuned is not running"); - else if (active == "none") - status = _("Tuned is off"); - else if (active == recommended) - status = _("This system is using the recommended profile"); - else - status = _("This system is using a custom profile"); - - setBtnText(state == "running" ? active : _("none")); - setState(state); - setStatus(status); - }) - .catch((ex) => { - console.warn("failed to poll tuned", ex); - - setBtnText("error"); - setStatus(_("Communication with tuned has failed")); - }); - }, [poll, setBtnText, setState, setStatus]); - - useEvent(superuser, "changed"); - useEvent(tunedService, "changed", () => updateButton()); - - useEffect(() => { - updateButton(); - }, [updateButton]); - - const showDialog = () => { - Dialogs.show(); + const oldServiceState = useRef(null); + + const tunedService = useInit(() => service.proxy("tuned.service")); + + async function update_with_tuned(tuned) { + try { + const { state, active, recommended } = await poll_tuned_state(tuned, tunedService); + let status; + + if (state == "not-installed") + status = _("Tuned is not available"); + else if (state == "not-running") + status = _("Tuned is not running"); + else if (active == "none") + status = _("Tuned is off"); + else if (active == recommended) + status = _("This system is using the recommended profile"); + else + status = _("This system is using a custom profile"); + setBtnText(state == "running" ? active : _("none")); + setState(state); + setStatus(status); + } catch (ex) { + console.warn("failed to poll tuned", ex); + + setBtnText("error"); + setStatus(_("Communication with tuned has failed")); + } + } + + async function update() { + const tuned = cockpit.dbus("com.redhat.tuned", { superuser: "try" }); + await update_with_tuned(tuned); + tuned.close(); + } + + useEvent(superuser, "reconnect", update); + useEvent(tunedService, "changed", () => { + // We get a flood of "changed" events sometimes without the + // state actually changing. So let's protect against that. + if (oldServiceState.current != tunedService.state) { + oldServiceState.current = tunedService.state; + update(); + } + }); + + const showDialog = async () => { + await tunedService.start(); + const tuned = cockpit.dbus("com.redhat.tuned", { superuser: "try" }); + await Dialogs.run(TunedDialog, { tunedDbus: tuned, tunedService }); + // Tuned does not send any change notifications... + await update_with_tuned(tuned); + tuned.close(); }; return ( @@ -125,12 +130,10 @@ export const TunedPerformanceProfile = () => { }; const TunedDialog = ({ - updateButton, - poll, tunedDbus, tunedService, + dialogResult, }) => { - const Dialogs = useDialogs(); const [activeProfile, setActiveProfile] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState(); @@ -182,8 +185,6 @@ const TunedDialog = ({ console.warn("Failed to disable tuned profile:", results); return Promise.reject(_("Failed to disable tuned profile")); } - - updateButton(); }); } else { promise = tunedDbus.call('/Tuned', 'com.redhat.tuned.control', 'switch_profile', [selected]) @@ -193,14 +194,12 @@ const TunedDialog = ({ console.warn("Failed to switch profile:", results); return Promise.reject(results[0][1] || _("Failed to switch profile")); } - - updateButton(); }); } return promise .then(setService) - .then(Dialogs.close) + .then(() => dialogResult.resolve()) .catch(setError); }; @@ -250,7 +249,7 @@ const TunedDialog = ({ }); }; - return poll() + return poll_tuned_state(tunedDbus, tunedService) .then(res => { const { state, active, recommended } = res; if (state != "running") { @@ -266,12 +265,10 @@ const TunedDialog = ({ .catch(setError); }; - tunedService.start() - .then(updateButton) - .then(withTuned) + withTuned() .catch(setError) .finally(() => setLoading(false)); - }, [updateButton, poll, tunedService, tunedDbus]); + }, [tunedService, tunedDbus]); const help = ( dialogResult.resolve()} title={_("Change performance profile")} footer={ <> -