diff mbox series

glib-2.0: backport memory monitor test fixes

Message ID 20240213142211.3517035-1-ross.burton@arm.com
State Accepted, archived
Commit 311d2606a70528e14093dd93178a2c7170718333
Headers show
Series glib-2.0: backport memory monitor test fixes | expand

Commit Message

Ross Burton Feb. 13, 2024, 2:22 p.m. UTC
From: Ross Burton <ross.burton@arm.com>

We've been seeing the memory-monitor-dbus test case fail occasionally
on the autobuilder.  Luckily there have been a series of fixes upstream
to fix races in the test case, so backport these and hopefully they
fix the issue.

[ YOCTO #15362 ]

Signed-off-by: Ross Burton <ross.burton@arm.com>
---
 .../glib-2.0/glib-2.0/memory-monitor.patch    | 364 ++++++++++++++++++
 meta/recipes-core/glib-2.0/glib-2.0_2.78.3.bb |   1 +
 2 files changed, 365 insertions(+)
 create mode 100644 meta/recipes-core/glib-2.0/glib-2.0/memory-monitor.patch
diff mbox series

Patch

diff --git a/meta/recipes-core/glib-2.0/glib-2.0/memory-monitor.patch b/meta/recipes-core/glib-2.0/glib-2.0/memory-monitor.patch
new file mode 100644
index 00000000000..f9dfc5bb05e
--- /dev/null
+++ b/meta/recipes-core/glib-2.0/glib-2.0/memory-monitor.patch
@@ -0,0 +1,364 @@ 
+From df7506918efc8748dac8b8398021e1b79d694ba4 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <philip@tecnocode.co.uk>
+Date: Tue, 23 Jan 2024 11:16:52 +0000
+Subject: [PATCH] Merge branch '2887-memory-monitor-tests' into 'main'
+
+tests: Fix race condition in memory-monitor-dbus.test
+
+Closes #2887
+
+See merge request GNOME/glib!3844
+
+Hopefully these commits fix the occasional failures we've been seeing:
+https://bugzilla.yoctoproject.org/show_bug.cgi?id=15362
+
+Upstream-Status: Backport
+Signed-off-by: Ross Burton <ross.burton@arm.com>
+---
+ gio/tests/memory-monitor-dbus.py.in          | 64 +++++++++++++-------
+ gio/tests/memory-monitor-portal.py.in        | 54 ++++++++++-------
+ gio/tests/power-profile-monitor-dbus.py.in   | 35 ++++++-----
+ gio/tests/power-profile-monitor-portal.py.in | 34 ++++++-----
+ 4 files changed, 113 insertions(+), 74 deletions(-)
+
+diff --git a/gio/tests/memory-monitor-dbus.py.in b/gio/tests/memory-monitor-dbus.py.in
+index bf3291847..7aae01e70 100755
+--- a/gio/tests/memory-monitor-dbus.py.in
++++ b/gio/tests/memory-monitor-dbus.py.in
+@@ -16,7 +16,6 @@ import sys
+ import subprocess
+ import fcntl
+ import os
+-import time
+ 
+ import taptestrunner
+ 
+@@ -57,53 +56,74 @@ try:
+             fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+             self.last_warning = -1
+             self.dbusmock = dbus.Interface(self.obj_lmm, dbusmock.MOCK_IFACE)
++
++            try:
++                self.wait_for_bus_object('org.freedesktop.LowMemoryMonitor',
++                                        '/org/freedesktop/LowMemoryMonitor',
++                                        system_bus=True)
++            except:
++                raise
++
+             self.memory_monitor = Gio.MemoryMonitor.dup_default()
++            assert("GMemoryMonitorDBus" in str(self.memory_monitor))
+             self.memory_monitor.connect("low-memory-warning", self.memory_warning_cb)
+             self.mainloop = GLib.MainLoop()
+             self.main_context = self.mainloop.get_context()
+ 
++            # The LowMemoryMonitor API is stateless: it doesn’t expose any
++            # properties, just a warning signal. Emit the signal in a loop until
++            # the GMemoryMonitor instance has initialised and synchronised to
++            # the right state.
++            def emit_warning(level):
++                self.dbusmock.EmitWarning(level)
++                return GLib.SOURCE_CONTINUE
++
++            idle_id = GLib.idle_add(emit_warning, 0)
++            while self.last_warning != 0:
++                self.main_context.iteration(True)
++            GLib.source_remove(idle_id)
++
+         def tearDown(self):
+             self.p_mock.terminate()
+             self.p_mock.wait()
+ 
+-        def assertEventually(self, condition, message=None, timeout=50):
++        def assertEventually(self, condition, message=None, timeout=5):
+             '''Assert that condition function eventually returns True.
+ 
+-            Timeout is in deciseconds, defaulting to 50 (5 seconds). message is
++            Timeout is in seconds, defaulting to 5 seconds. message is
+             printed on failure.
+             '''
+-            while timeout >= 0:
+-                context = GLib.MainContext.default()
+-                while context.iteration(False):
+-                    pass
+-                if condition():
+-                    break
+-                timeout -= 1
+-                time.sleep(0.1)
+-            else:
+-                self.fail(message or 'timed out waiting for ' + str(condition))
++            if not message:
++                message = 'timed out waiting for ' + str(condition)
++
++            def timed_out_cb(message):
++                self.fail(message)
++                return GLib.SOURCE_REMOVE
++
++            timeout_source = GLib.timeout_source_new_seconds(timeout)
++            timeout_source.set_callback(timed_out_cb, message)
++            timeout_source.attach(self.main_context)
++
++            while not condition():
++                self.main_context.iteration(True)
++
++            timeout_source.destroy()
+ 
+         def memory_warning_cb(self, monitor, level):
++            print("Received memory warning signal, level", level)
+             self.last_warning = level
+             self.main_context.wakeup()
+ 
+         def test_low_memory_warning_signal(self):
+             '''LowMemoryWarning signal'''
+ 
+-            # Wait 2 seconds
+-            timeout = 2
+-            while timeout > 0:
+-                time.sleep(0.5)
+-                timeout -= 0.5
+-                self.main_context.iteration(False)
+-
+             self.dbusmock.EmitWarning(100)
+             # Wait 2 seconds or until warning
+-            self.assertEventually(lambda: self.last_warning == 100, "'100' low-memory warning not received", 20)
++            self.assertEventually(lambda: self.last_warning == 100, "'100' low-memory warning not received", 2)
+ 
+             self.dbusmock.EmitWarning(255)
+             # Wait 2 seconds or until warning
+-            self.assertEventually(lambda: self.last_warning == 255, "'255' low-memory warning not received", 20)
++            self.assertEventually(lambda: self.last_warning == 255, "'255' low-memory warning not received", 2)
+ 
+ except ImportError as e:
+     @unittest.skip("Cannot import %s" % e.name)
+diff --git a/gio/tests/memory-monitor-portal.py.in b/gio/tests/memory-monitor-portal.py.in
+index 748cee850..f570508f9 100755
+--- a/gio/tests/memory-monitor-portal.py.in
++++ b/gio/tests/memory-monitor-portal.py.in
+@@ -16,7 +16,6 @@ import sys
+ import subprocess
+ import fcntl
+ import os
+-import time
+ 
+ import taptestrunner
+ 
+@@ -80,26 +79,44 @@ try:
+             self.mainloop = GLib.MainLoop()
+             self.main_context = self.mainloop.get_context()
+ 
++            # The LowMemoryMonitor API is stateless: it doesn’t expose any
++            # properties, just a warning signal. Emit the signal in a loop until
++            # the GMemoryMonitor instance has initialised and synchronised to
++            # the right state.
++            def emit_warning(level):
++                self.dbusmock.EmitWarning(level)
++                return GLib.SOURCE_CONTINUE
++
++            idle_id = GLib.idle_add(self.emit_warning, 0)
++            while self.last_warning != 0:
++                self.main_context.iteration(True)
++            GLib.source_remove(idle_id)
++
+         def tearDown(self):
+             self.p_mock.terminate()
+             self.p_mock.wait()
+ 
+-        def assertEventually(self, condition, message=None, timeout=50):
++        def assertEventually(self, condition, message=None, timeout=5):
+             '''Assert that condition function eventually returns True.
+ 
+-            Timeout is in deciseconds, defaulting to 50 (5 seconds). message is
++            Timeout is in seconds, defaulting to 5 seconds. message is
+             printed on failure.
+             '''
+-            while timeout >= 0:
+-                context = GLib.MainContext.default()
+-                while context.iteration(False):
+-                    pass
+-                if condition():
+-                    break
+-                timeout -= 1
+-                time.sleep(0.1)
+-            else:
+-                self.fail(message or 'timed out waiting for ' + str(condition))
++            if not message:
++                message = 'timed out waiting for ' + str(condition)
++
++            def timed_out_cb(message):
++                self.fail(message)
++                return GLib.SOURCE_REMOVE
++
++            timeout_source = GLib.timeout_source_new_seconds(timeout)
++            timeout_source.set_callback(timed_out_cb, message)
++            timeout_source.attach(self.main_context)
++
++            while not condition():
++                self.main_context.iteration(True)
++
++            timeout_source.destroy()
+ 
+         def portal_memory_warning_cb(self, monitor, level):
+             self.last_warning = level
+@@ -108,20 +125,13 @@ try:
+         def test_low_memory_warning_portal_signal(self):
+             '''LowMemoryWarning signal'''
+ 
+-            # Wait 2 seconds
+-            timeout = 2
+-            while timeout > 0:
+-                time.sleep(0.5)
+-                timeout -= 0.5
+-                self.main_context.iteration(False)
+-
+             self.dbusmock.EmitWarning(100)
+             # Wait 2 seconds or until warning
+-            self.assertEventually(lambda: self.last_warning == 100, "'100' low-memory warning not received", 20)
++            self.assertEventually(lambda: self.last_warning == 100, "'100' low-memory warning not received", 2)
+ 
+             self.dbusmock.EmitWarning(255)
+             # Wait 2 seconds or until warning
+-            self.assertEventually(lambda: self.last_warning == 255, "'255' low-memory warning not received", 20)
++            self.assertEventually(lambda: self.last_warning == 255, "'255' low-memory warning not received", 2)
+ 
+ except ImportError as e:
+     @unittest.skip("Cannot import %s" % e.name)
+diff --git a/gio/tests/power-profile-monitor-dbus.py.in b/gio/tests/power-profile-monitor-dbus.py.in
+index 06e594f4a..f955afc80 100755
+--- a/gio/tests/power-profile-monitor-dbus.py.in
++++ b/gio/tests/power-profile-monitor-dbus.py.in
+@@ -16,7 +16,6 @@ import sys
+ import subprocess
+ import fcntl
+ import os
+-import time
+ 
+ import taptestrunner
+ 
+@@ -58,6 +57,7 @@ try:
+             self.power_saver_enabled = False
+             self.dbus_props = dbus.Interface(self.obj_ppd, dbus.PROPERTIES_IFACE)
+             self.power_profile_monitor = Gio.PowerProfileMonitor.dup_default()
++            assert("GPowerProfileMonitorDBus" in str(self.power_profile_monitor))
+             self.power_profile_monitor.connect("notify::power-saver-enabled", self.power_saver_enabled_cb)
+             self.mainloop = GLib.MainLoop()
+             self.main_context = self.mainloop.get_context()
+@@ -66,22 +66,27 @@ try:
+             self.p_mock.terminate()
+             self.p_mock.wait()
+ 
+-        def assertEventually(self, condition, message=None, timeout=50):
++        def assertEventually(self, condition, message=None, timeout=5):
+             '''Assert that condition function eventually returns True.
+ 
+-            Timeout is in deciseconds, defaulting to 50 (5 seconds). message is
++            Timeout is in seconds, defaulting to 5 seconds. message is
+             printed on failure.
+             '''
+-            while timeout >= 0:
+-                context = GLib.MainContext.default()
+-                while context.iteration(False):
+-                    pass
+-                if condition():
+-                    break
+-                timeout -= 1
+-                time.sleep(0.1)
+-            else:
+-                self.fail(message or 'timed out waiting for ' + str(condition))
++            if not message:
++                message = 'timed out waiting for ' + str(condition)
++
++            def timed_out_cb(message):
++                self.fail(message)
++                return GLib.SOURCE_REMOVE
++
++            timeout_source = GLib.timeout_source_new_seconds(timeout)
++            timeout_source.set_callback(timed_out_cb, message)
++            timeout_source.attach(self.main_context)
++
++            while not condition():
++                self.main_context.iteration(True)
++
++            timeout_source.destroy()
+ 
+         def power_saver_enabled_cb(self, spec, data):
+             self.power_saver_enabled = self.power_profile_monitor.get_power_saver_enabled()
+@@ -92,10 +97,10 @@ try:
+ 
+             self.assertEqual(self.power_profile_monitor.get_power_saver_enabled(), False)
+             self.dbus_props.Set('net.hadess.PowerProfiles', 'ActiveProfile', dbus.String('power-saver', variant_level=1))
+-            self.assertEventually(lambda: self.power_saver_enabled == True, "power-saver didn't become enabled", 10)
++            self.assertEventually(lambda: self.power_saver_enabled == True, "power-saver didn't become enabled", 1)
+ 
+             self.dbus_props.Set('net.hadess.PowerProfiles', 'ActiveProfile', dbus.String('balanced', variant_level=1))
+-            self.assertEventually(lambda: self.power_saver_enabled == False, "power-saver didn't become disabled", 10)
++            self.assertEventually(lambda: self.power_saver_enabled == False, "power-saver didn't become disabled", 1)
+ 
+ except ImportError as e:
+     @unittest.skip("Cannot import %s" % e.name)
+diff --git a/gio/tests/power-profile-monitor-portal.py.in b/gio/tests/power-profile-monitor-portal.py.in
+index 09e9a450d..ad2abf621 100755
+--- a/gio/tests/power-profile-monitor-portal.py.in
++++ b/gio/tests/power-profile-monitor-portal.py.in
+@@ -16,7 +16,6 @@ import sys
+ import subprocess
+ import fcntl
+ import os
+-import time
+ 
+ import taptestrunner
+ 
+@@ -90,22 +89,27 @@ try:
+             self.p_mock.terminate()
+             self.p_mock.wait()
+ 
+-        def assertEventually(self, condition, message=None, timeout=50):
++        def assertEventually(self, condition, message=None, timeout=5):
+             '''Assert that condition function eventually returns True.
+ 
+-            Timeout is in deciseconds, defaulting to 50 (5 seconds). message is
++            Timeout is in seconds, defaulting to 5 seconds. message is
+             printed on failure.
+             '''
+-            while timeout >= 0:
+-                context = GLib.MainContext.default()
+-                while context.iteration(False):
+-                    pass
+-                if condition():
+-                    break
+-                timeout -= 1
+-                time.sleep(0.1)
+-            else:
+-                self.fail(message or 'timed out waiting for ' + str(condition))
++            if not message:
++                message = 'timed out waiting for ' + str(condition)
++
++            def timed_out_cb(message):
++                self.fail(message)
++                return GLib.SOURCE_REMOVE
++
++            timeout_source = GLib.timeout_source_new_seconds(timeout)
++            timeout_source.set_callback(timed_out_cb, message)
++            timeout_source.attach(self.main_context)
++
++            while not condition():
++                self.main_context.iteration(True)
++
++            timeout_source.destroy()
+ 
+         def power_saver_enabled_cb(self, spec, data):
+             self.power_saver_enabled = self.power_profile_monitor.get_power_saver_enabled()
+@@ -116,10 +120,10 @@ try:
+ 
+             self.assertEqual(self.power_profile_monitor.get_power_saver_enabled(), False)
+             self.dbus_props.Set('net.hadess.PowerProfiles', 'ActiveProfile', dbus.String('power-saver', variant_level=1))
+-            self.assertEventually(lambda: self.power_saver_enabled == True, "power-saver didn't become enabled", 10)
++            self.assertEventually(lambda: self.power_saver_enabled == True, "power-saver didn't become enabled", 1)
+ 
+             self.dbus_props.Set('net.hadess.PowerProfiles', 'ActiveProfile', dbus.String('balanced', variant_level=1))
+-            self.assertEventually(lambda: self.power_saver_enabled == False, "power-saver didn't become disabled", 10)
++            self.assertEventually(lambda: self.power_saver_enabled == False, "power-saver didn't become disabled", 1)
+ 
+         def test_power_profile_power_saver_enabled_portal_default(self):
+             '''power-saver-enabled property default value'''
+-- 
+2.34.1
+
diff --git a/meta/recipes-core/glib-2.0/glib-2.0_2.78.3.bb b/meta/recipes-core/glib-2.0/glib-2.0_2.78.3.bb
index 2729b2a5963..42814ba5441 100644
--- a/meta/recipes-core/glib-2.0/glib-2.0_2.78.3.bb
+++ b/meta/recipes-core/glib-2.0/glib-2.0_2.78.3.bb
@@ -14,6 +14,7 @@  SRC_URI = "${GNOME_MIRROR}/glib/${SHRT_VER}/glib-${PV}.tar.xz \
            file://0001-Do-not-write-bindir-into-pkg-config-files.patch \
            file://0001-meson-Run-atomics-test-on-clang-as-well.patch \
            file://0001-gio-tests-resources.c-comment-out-a-build-host-only-.patch \
+           file://memory-monitor.patch \
            file://native-gtkdoc.patch \
            "
 SRC_URI:append:class-native = " file://relocate-modules.patch \