aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHiroyuki Ishii <ishii.hiroyuki002@jp.panasonic.com>2021-06-29 17:13:03 +0900
committerJan-Simon Moeller <jsmoeller@linuxfoundation.org>2021-07-13 21:16:28 +0000
commit9a6bed4d97b22de2dfdd32f1128aa30acdd9e439 (patch)
treedf0b458b9a5ca918640fcff53a3405ccdc9d96f4
parent00488a311c18f03bf7a30b244fcd373c8c30c42e (diff)
qt5: Fix timer leak in qtwayland to avoid animations being sluggish
With long-running qt applications which have fluid animations in wayland environment, the animation becomes obviously sluggish because massive amount of memcpy() is called through a constructor being placed in a loop in QTimerInfoList::timerInsert() function. This is caused by a timer-index leak bug of qtwayland, which is already reported to the qt project as following ticket. https://bugreports.qt.io/browse/QTBUG-79838 Unfortunately QTBUG-79838 is still open because maintenance of non-commercial version of qt5 is already stopped. However, we've confirmed that the patch attached to that ticket works fine except for that part of it is no longer needed due to the deletion of the code. So let's apply only effective part of it. Bug-AGL: SPEC-3991 Signed-off-by: Hiroyuki Ishii <ishii.hiroyuki002@jp.panasonic.com> Change-Id: Ib148b81aabb98e8df10c1414cdbfe26f7ddf09a6 Reviewed-on: https://gerrit.automotivelinux.org/gerrit/c/AGL/meta-agl/+/26466 Tested-by: Jenkins Job builder account ci-image-build: Jenkins Job builder account ci-image-boot-test: Jenkins Job builder account Reviewed-by: Jan-Simon Moeller <jsmoeller@linuxfoundation.org>
-rw-r--r--meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland/0001-Fix-timer-leak-and-a-potential-race.patch42
-rw-r--r--meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_%.bbappend1
-rw-r--r--meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_aglcore.inc6
3 files changed, 49 insertions, 0 deletions
diff --git a/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland/0001-Fix-timer-leak-and-a-potential-race.patch b/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland/0001-Fix-timer-leak-and-a-potential-race.patch
new file mode 100644
index 000000000..642eabc4b
--- /dev/null
+++ b/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland/0001-Fix-timer-leak-and-a-potential-race.patch
@@ -0,0 +1,42 @@
+From f4d3297e6705cc524729d629bf94db11841dbb24 Mon Sep 17 00:00:00 2001
+From: Simon Yuan <simon.yuan@navico.com>
+Date: Thu, 7 Nov 2019 09:22:37 +1300
+Subject: [PATCH] Fix timer leak and a potential race
+
+The callback timer is now killed immediately before starting a new timer, this
+makes sure there is always a single active callback timer. It's unclear why
+killing the timer in a separate lambda doesn't always kill the timer in time,
+the hypothesis is that if killing the timer comes after starting a new one, then
+the previous timer is now left dangling. Whatever the reason is, it makes even
+more sense to kill the timer in the same lamda and immediately before starting a
+new timer anyway.
+---
+ src/client/qwaylandwindow.cpp | 11 +++++------
+ 1 file changed, 5 insertions(+), 6 deletions(-)
+
+diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp
+index 0df99d9f..93b46bf1 100644
+--- a/src/client/qwaylandwindow.cpp
++++ b/src/client/qwaylandwindow.cpp
+@@ -1136,13 +1136,12 @@ void QWaylandWindow::handleUpdate()
+ mWaitingForFrameCallback = true;
+ mWaitingForUpdate = false;
+
+- // Stop current frame timer if any, can't use killTimer directly, see comment above.
+- int fcbId = mFrameCallbackTimerId.fetchAndStoreOrdered(-1);
+- if (fcbId != -1)
+- QMetaObject::invokeMethod(this, [this, fcbId] { killTimer(fcbId); }, Qt::QueuedConnection);
+-
+ // Start a timer for handling the case when the compositor stops sending frame callbacks.
+- QMetaObject::invokeMethod(this, [this] { // Again; can't do it directly
++ // Can't use killTimer directly, see comment above.
++ QMetaObject::invokeMethod(this, [this] {
++ int fcbId = mFrameCallbackTimerId.fetchAndStoreOrdered(-1);
++ if (fcbId != -1)
++ killTimer(fcbId);
+ if (mWaitingForFrameCallback)
+ mFrameCallbackTimerId = startTimer(100);
+ }, Qt::QueuedConnection);
+--
+2.25.1
+
diff --git a/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_%.bbappend b/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_%.bbappend
new file mode 100644
index 000000000..aa55c3a0c
--- /dev/null
+++ b/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_%.bbappend
@@ -0,0 +1 @@
+require ${@bb.utils.contains('AGL_FEATURES', 'aglcore', 'qtwayland_aglcore.inc', '', d)}
diff --git a/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_aglcore.inc b/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_aglcore.inc
new file mode 100644
index 000000000..ef5d0c7b5
--- /dev/null
+++ b/meta-agl-core/dynamic-layers/meta-qt5/recipes-qt/qt5/qtwayland_aglcore.inc
@@ -0,0 +1,6 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/qtwayland:"
+
+# Patch reported in https://bugreports.qt.io/browse/QTBUG-79838, not upstreamed
+SRC_URI += " \
+ file://0001-Fix-timer-leak-and-a-potential-race.patch \
+ "
r: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
var afb = new AFB("api", "mysecret");
var ws;
var evtIdx = 0;
var count = 0;

var amazon = new AMAZON();
var amazonCbl;

//**********************************************
// Logger
//**********************************************
var log = {
  command: function (api, verb, query) {
    console.log("subscribe api=" + api + " verb=" + verb + " query=", query);
    var question = afb.url + "/" + api + "/" + verb + "?query=" + JSON.stringify(query);
    log._write("question", count + ": " + log.syntaxHighlight(question));
  },

  event: function (obj) {
    console.log("gotevent:" + JSON.stringify(obj));
    log._write("outevt", (evtIdx++) + ": " + JSON.stringify(obj));
  },

  reply: function (obj) {
    console.log("replyok:" + JSON.stringify(obj));
    log._write("output", count + ": OK: " + log.syntaxHighlight(obj));
  },

  error: function (obj) {
    console.log("replyerr:" + JSON.stringify(obj));
    log._write("output", count + ": ERROR: " + log.syntaxHighlight(obj));
  },

  _write: function (element, msg) {
    var el = document.getElementById(element);
    el.innerHTML += msg + '\n';

    // auto scroll down
    setTimeout(function () {
      el.scrollTop = el.scrollHeight;
    }, 100);
  },

  syntaxHighlight: function (json) {
    if (typeof json !== 'string') {
      json = JSON.stringify(json, undefined, 2);
    }
    json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
      var cls = 'number';
      if (/^"/.test(match)) {
        if (/:$/.test(match)) {
          cls = 'key';
        } else {
          cls = 'string';
        }
      } else if (/true|false/.test(match)) {
        cls = 'boolean';
      } else if (/null/.test(match)) {
        cls = 'null';
      }
      return '<span class="' + cls + '">' + match + '</span>';
    });
  },
};

//**********************************************
// Generic function to call binder
//***********************************************
function callbinder(api, verb, query) {
  log.command(api, verb, query);

  // ws.call return a Promise
  return ws.call(api + '/' + verb, query)
    .then(function (res) {
      log.reply(res);
      count++;
      return res;
    })
    .catch(function (err) {
      log.reply(err);
      count++;
      throw err;
    });
};

//**********************************************
// Init - establish Websocket connection
//**********************************************
function init(elemID, api, verb, query) {

  function onopen() {
    document.getElementById("main").style.visibility = "visible";
    document.getElementById("connected").innerHTML = "Binder WS Active";
    document.getElementById("connected").style.background = "lightgreen";
    ws.onevent("*", log.event);
    // Fetch and render voice agents.
    fetchAndRenderVoiceAgents();
  }

  function onabort() {
    document.getElementById("main").style.visibility = "hidden";
    document.getElementById("connected").innerHTML = "Connected Closed";
    document.getElementById("connected").style.background = "red";
  }

  ws = new afb.ws(onopen, onabort);
}

function clearPre(preId) {
  const pre = document.getElementById(preId);
  while (pre && pre.firstChild) {
    pre.removeChild(pre.firstChild);
  }
}

function fetchAndRenderVoiceAgents() {
  const agentsDiv = document.getElementById('agentsDiv');
  while (agentsDiv.firstChild) {
    agentsDiv.removeChild(agentsDiv.firstChild);
  }

  const api = 'vshl';
  const verb = 'enumerateVoiceAgents';
  const query = {};

  log.command(api, verb, query);

  return ws.call(api + '/' + verb, query)
    .then(function (res) {
      log.reply(res);
      for (let index = 0; index < res.response.agents.length; ++index) {
        let voiceAgent = res.response.agents[index];
        addVoiceAgent(agentsDiv, voiceAgent, res.response.default == voiceAgent.id);
      }
    })
    .catch(function (err) {
      log.reply(err);
      console.log(JSON.stringify(err));
    });
}

function addVoiceAgent(containerDiv, voiceAgent, isDefault) {
  const agentDiv = document.createElement("div");

  const agentName = document.createElement("h2");
  agentName.innerHTML = voiceAgent.name;
  agentDiv.appendChild(agentName);

  const agentDescription = document.createElement("p");
  agentDescription.innerHTML = voiceAgent.description;
  agentDiv.appendChild(agentDescription);

  if (!isDefault) {
    const setDefaultBtn = document.createElement("button");
    setDefaultBtn.addEventListener('click', (evt) => {
      const query = {"id": voiceAgent.id};
      callbinder('vshl', 'setDefaultVoiceAgent', query);
      fetchAndRenderVoiceAgents();
    });
    setDefaultBtn.innerHTML = 'SetDefault';
    agentDiv.appendChild(setDefaultBtn);
  }

  const subscribeBtn = document.createElement("button");
  subscribeBtn.addEventListener('click', (evt) => {
    showAgentEventChooserDialog(voiceAgent.id);
  });
  subscribeBtn.innerHTML = 'Subscribe';
  agentDiv.appendChild(subscribeBtn);

  // Login implementation for Alexa Voice Agent
  if (voiceAgent.name == "Alexa") {
    amazonCbl = new amazon.cbl();
    if (typeof(Storage) !== "undefined" &&
        localStorage.getItem("access_token") !== null &&
        localStorage.getItem("refresh_token") !== null) {
      amazonCbl.refreshToken(voiceAgent);
    } else {
      const loginWithAmazonBtn = document.createElement("button");
      loginWithAmazonBtn.addEventListener('click', (evt) => {
        loginWithAmazonBtn.style.visibility = "hidden";
        amazonCbl.login(voiceAgent);
      });
      loginWithAmazonBtn.innerHTML = 'Login With Amazon!!';
      agentDiv.appendChild(loginWithAmazonBtn);
    }
  }

  containerDiv.appendChild(agentDiv);
}

function showAgentEventChooserDialog(voiceAgentId) {
  const modal = document.getElementById('agent-event-chooser');
  const subscribeBtn = document.getElementById('agent-subscribe-btn');

  subscribeBtn.addEventListener('click', (evt) => {
    const authState = document.getElementById('authstate').checked;
    const dialogState = document.getElementById('dialogstate').checked;
    const connectionState = document.getElementById('connectionstate').checked;

    const query = {
      "va_id": voiceAgentId,
      "events":[]
    };
    if (authState)
      query.events.push('voice_authstate_event');
    if (dialogState)
      query.events.push('voice_dialogstate_event');
    if (connectionState)
      query.events.push('voice_connectionstate_event');

    callbinder('vshl', 'subscribe', query);
    modal.close();
  });

  // makes modal appear (adds `open` attribute)
  modal.showModal();
}

function showTemplateUIEventChooserDialog() {
  const modal = document.getElementById('templateui-event-chooser');
  const subscribeBtn = document.getElementById('templateui-subscribe-btn');

  subscribeBtn.addEventListener('click', (evt) => {
    const renderTemplate = document.getElementById('render_template').checked;
    const clearTemplate = document.getElementById('clear_template').checked;
    const renderPlayerInfo = document.getElementById('render_player_info').checked;
    const clearPlayerInfo = document.getElementById('clear_player_info').checked;

    const query = {"actions":[]};

    if (renderTemplate)
      query.actions.push('render_template');
    if (clearTemplate)
      query.actions.push('clear_template');
    if (renderPlayerInfo)
      query.actions.push('render_player_info');
    if (clearPlayerInfo)
      query.actions.push('clear_player_info');

    callbinder('vshl', 'guiMetadata/subscribe', query);
    modal.close();
  });

  // makes modal appear (adds `open` attribute)
  modal.showModal();
}