Hi,

I do not experience the lyx-sluggish problem myself so it's difficult test any 
proposed solutions.
Tried to push claude to investigate from many angles, but never got something 
which would exactly
fit the reports.

I have candidate for a leak when I relax the demand it's 2.5.x only. So it's 
present in 2.4 as well, 
but perhaps the windows Qt6 transition made it more pronounced?

I am looking for someone who 1) experiences the leak itself and 2) can compile 
lyx and test proposed fix(es).

Anyone around?

The patch to test is attached.
Pavel

PS: for @devs this is Claude's theory:
-----------------------
  GuiToolbar::update() runs on every keypress and emits updated(), which is 
connected to DynamicMenuButton::updateTriggered(). For the "textstyle-apply" 
and "paste" buttons, that slot rebuilds the whole  dropdown menu each time:

  m->clear();
  ...
  Action * act = new Action(func, QIcon(), toqstr(lb), toqstr(s), this);
  m->addAction(act);

  The actions are parented to this (the button), not to the menu. 
QMenu::clear() only deletes actions the menu owns, so these are merely detached 
and stay alive as children of the button - for the lifetime of  the window. 
Every keystroke therefore allocates several new QActions (plus their QStrings) 
that are never freed.
 
  Measured on master (Qt5, Linux) with heaptrack, typing into a math-free 
document, memory still held at exit:
 
  - 100 chars: +0.41 MB, 400 chars: +1.66 MB, 800 chars: +3.32 MB
  - i.e. ~4.15 kB per keystroke, perfectly linear and unbounded 

  Deleting the stale actions before rebuilding drops this from 4.14 to 0.99 kB 
per keystroke (~76%).
------
diff --git a/src/frontends/qt/GuiToolbar.cpp b/src/frontends/qt/GuiToolbar.cpp
index 2e0a6d5819..ddb957273a 100644
--- a/src/frontends/qt/GuiToolbar.cpp
+++ b/src/frontends/qt/GuiToolbar.cpp
@@ -423,6 +423,11 @@ void DynamicMenuButton::updateTriggered()
 			   && !d->func_requests_.empty()
 			   && lyx::getStatus(d->func_requests_.front()).enabled());
 	} else if (menutype == "textstyle-apply") {
+		// LEAK-TEST: QMenu::clear() does NOT delete these actions, because
+		// they are parented to this button, not to the menu. Without this
+		// they accumulate on every toolbar update (i.e. every keystroke).
+		for (QAction * a : m->actions())
+			delete a;
 		m->clear();
 		setPopupMode(QToolButton::MenuButtonPopup);
 		if (!bv) {
@@ -463,6 +468,11 @@ void DynamicMenuButton::updateTriggered()
 		setEnabled(lyx::getStatus(FuncRequest(LFUN_TEXTSTYLE_APPLY)).enabled()
 			   || lyx::getStatus(FuncRequest(LFUN_FONT_DEFAULT)).enabled());
 	} else if (menutype == "paste") {
+		// LEAK-TEST: QMenu::clear() does NOT delete these actions, because
+		// they are parented to this button, not to the menu. Without this
+		// they accumulate on every toolbar update (i.e. every keystroke).
+		for (QAction * a : m->actions())
+			delete a;
 		m->clear();
 		setPopupMode(QToolButton::MenuButtonPopup);
 		Action * default_action = new Action(FuncRequest(LFUN_PASTE),
@@ -496,6 +506,11 @@ void DynamicMenuButton::updateTriggered()
 void DynamicMenuButton::loadFlexInsets()
 {
 	QMenu * m = menu();
+	// LEAK-TEST: QMenu::clear() does NOT delete these actions, because
+	// they are parented to this button, not to the menu. Without this
+	// they accumulate on every toolbar update (i.e. every keystroke).
+	for (QAction * a : m->actions())
+		delete a;
 	m->clear();
 	d->func_requests_.clear();
 	string const & menutype = tbitem_.name;
-- 
lyx-devel mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-devel

Reply via email to