diff --git a/gio/giomodule.c b/gio/giomodule.c
index 7491fd433..38761e4fd 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -55,6 +55,9 @@
#ifdef __linux__
#include "gmemorymonitorpsi.h"
#endif
+#ifdef HAVE_SYSINFO
+#include "gmemorymonitorpoll.h"
+#endif
#include "gpowerprofilemonitor.h"
#include "gpowerprofilemonitordbus.h"
#include "gpowerprofilemonitorportal.h"
@@ -1087,6 +1090,9 @@ extern GType g_memory_monitor_dbus_get_type (void);
#ifdef __linux__
extern GType g_memory_monitor_psi_get_type (void);
#endif
+#ifdef HAVE_SYSINFO
+extern GType g_memory_monitor_poll_get_type (void);
+#endif
extern GType g_memory_monitor_portal_get_type (void);
extern GType g_memory_monitor_win32_get_type (void);
extern GType g_power_profile_monitor_dbus_get_type (void);
@@ -1369,6 +1375,9 @@ _g_io_modules_ensure_loaded (void)
g_type_ensure (g_memory_monitor_dbus_get_type ());
#ifdef __linux__
g_type_ensure (g_memory_monitor_psi_get_type ());
+#endif
+#ifdef HAVE_SYSINFO
+ g_type_ensure (g_memory_monitor_poll_get_type ());
#endif
g_type_ensure (g_memory_monitor_portal_get_type ());
g_type_ensure (g_network_monitor_portal_get_type ());
diff --git a/gio/gmemorymonitorpoll.c b/gio/gmemorymonitorpoll.c
new file mode 100644
index 000000000..d5d4e75f5
--- /dev/null
+++ b/gio/gmemorymonitorpoll.c
@@ -0,0 +1,273 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2025 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see .
+ */
+#include "config.h"
+
+#include "gcancellable.h"
+#include "gdbusnamewatching.h"
+#include "gdbusproxy.h"
+#include "ginitable.h"
+#include "gioerror.h"
+#include "giomodule-priv.h"
+#include "glib/gstdio.h"
+#include "glib/glib-private.h"
+#include "glibintl.h"
+#include "gmemorymonitor.h"
+#include "gmemorymonitorpoll.h"
+
+#include
+#include
+
+/**
+ * GMemoryMonitorPoll:
+ *
+ * A [iface@Gio.MemoryMonitor] which polls the system free/used
+ * memory ratio on a fixed timer.
+ *
+ * It polls, for example, every 10 seconds, and emits different
+ * [signal@Gio.MemoryMonitor::low-memory-warning] signals if it falls below several
+ * ‘low’ thresholds.
+ *
+ * The system free/used memory ratio is queried using [`sysinfo()`](man:sysinfo(2)).
+ *
+ * This is intended as a fallback implementation of [iface@Gio.MemoryMonitor] in case
+ * other, more performant, implementations are not supported on the system.
+ *
+ * Since: 2.86
+ */
+
+typedef enum {
+ PROP_MEM_FREE_RATIO = 1,
+ PROP_POLL_INTERVAL_MS,
+} GMemoryMonitorPollProperty;
+
+#define G_MEMORY_MONITOR_POLL_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable))
+
+static void g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *iface);
+static void g_memory_monitor_poll_initable_iface_init (GInitableIface *iface);
+
+struct _GMemoryMonitorPoll
+{
+ GMemoryMonitorBase parent_instance;
+
+ GMainContext *worker; /* (unowned) */
+ GSource *source_timeout; /* (owned) */
+
+ /* it overrides the default timeout when running the test */
+ unsigned int poll_interval_ms; /* zero to use the default */
+ gdouble mem_free_ratio;
+};
+
+/* the default monitor timeout */
+#define G_MEMORY_MONITOR_PSI_DEFAULT_SEC 10
+
+G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorPoll, g_memory_monitor_poll, G_TYPE_MEMORY_MONITOR_BASE,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ g_memory_monitor_poll_initable_iface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR,
+ g_memory_monitor_poll_iface_init)
+ _g_io_modules_ensure_extension_points_registered ();
+ g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ "poll",
+ 10))
+
+static void
+g_memory_monitor_poll_init (GMemoryMonitorPoll *mem_poll)
+{
+ mem_poll->mem_free_ratio = -1.0;
+}
+
+static void
+g_memory_monitor_poll_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
+
+ switch ((GMemoryMonitorPollProperty) prop_id)
+ {
+ case PROP_MEM_FREE_RATIO:
+ monitor->mem_free_ratio = g_value_get_double (value);
+ break;
+ case PROP_POLL_INTERVAL_MS:
+ monitor->poll_interval_ms = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_memory_monitor_poll_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
+
+ switch ((GMemoryMonitorPollProperty) prop_id)
+ {
+ case PROP_MEM_FREE_RATIO:
+ g_value_set_double (value, monitor->mem_free_ratio);
+ break;
+ case PROP_POLL_INTERVAL_MS:
+ g_value_set_uint (value, monitor->poll_interval_ms);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+g_memory_monitor_mem_ratio_cb (gpointer data)
+{
+ GMemoryMonitorPoll *monitor = (GMemoryMonitorPoll *) data;
+ gdouble mem_ratio;
+ GMemoryMonitorLowMemoryLevel warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID;
+
+ /* Should be executed in the worker context */
+ g_assert (g_main_context_is_owner (monitor->worker));
+
+ mem_ratio = g_memory_monitor_base_query_mem_ratio ();
+
+ if (mem_ratio < 0.0)
+ return G_SOURCE_REMOVE;
+
+ if (mem_ratio > 0.5)
+ return G_SOURCE_CONTINUE;
+
+ /* free ratio override */
+ if (monitor->mem_free_ratio >= 0.0)
+ mem_ratio = monitor->mem_free_ratio;
+
+ g_debug ("memory free ratio %f", mem_ratio);
+
+ if (mem_ratio < 0.2)
+ warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_CRITICAL;
+ else if (mem_ratio < 0.3)
+ warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_MEDIUM;
+ else if (mem_ratio < 0.4)
+ warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_LOW;
+
+ if (warning_level != G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID)
+ g_memory_monitor_base_send_event_to_user (G_MEMORY_MONITOR_BASE (monitor), warning_level);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+g_memory_monitor_poll_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (initable);
+ GSource *source;
+
+ if (monitor->poll_interval_ms > 0)
+ {
+ if (monitor->poll_interval_ms < G_TIME_SPAN_MILLISECOND)
+ source = g_timeout_source_new (monitor->poll_interval_ms);
+ else
+ source = g_timeout_source_new_seconds (monitor->poll_interval_ms / G_TIME_SPAN_MILLISECOND);
+ }
+ else
+ {
+ /* default 10 second */
+ source = g_timeout_source_new_seconds (G_MEMORY_MONITOR_PSI_DEFAULT_SEC);
+ }
+
+ g_source_set_callback (source, g_memory_monitor_mem_ratio_cb, monitor, NULL);
+ monitor->worker = GLIB_PRIVATE_CALL (g_get_worker_context) ();
+ g_source_attach (source, monitor->worker);
+ monitor->source_timeout = g_steal_pointer (&source);
+
+ return TRUE;
+}
+
+static void
+g_memory_monitor_poll_finalize (GObject *object)
+{
+ GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
+
+ g_source_destroy (monitor->source_timeout);
+ g_source_unref (monitor->source_timeout);
+
+ G_OBJECT_CLASS (g_memory_monitor_poll_parent_class)->finalize (object);
+}
+
+static void
+g_memory_monitor_poll_class_init (GMemoryMonitorPollClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = g_memory_monitor_poll_set_property;
+ object_class->get_property = g_memory_monitor_poll_get_property;
+ object_class->finalize = g_memory_monitor_poll_finalize;
+
+ /**
+ * GMemoryMonitorPoll:mem-free-ratio:
+ *
+ * Override the memory free ratio
+ *
+ * Since: 2.86
+ */
+ g_object_class_install_property (object_class,
+ PROP_MEM_FREE_RATIO,
+ g_param_spec_double ("mem-free-ratio", NULL, NULL,
+ -1.0, 1.0,
+ -1.0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GMemoryMonitorPoll:poll-interval-ms:
+ *
+ * Override the poll interval for monitoring the memory usage.
+ *
+ * The interval is in milliseconds. Zero means to use the default interval.
+ *
+ * Since: 2.86
+ */
+ g_object_class_install_property (object_class,
+ PROP_POLL_INTERVAL_MS,
+ g_param_spec_uint ("poll-interval-ms", NULL, NULL,
+ 0, G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+}
+
+static void
+g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *monitor_iface)
+{
+}
+
+static void
+g_memory_monitor_poll_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = g_memory_monitor_poll_initable_init;
+}
+
diff --git a/gio/gmemorymonitorpoll.h b/gio/gmemorymonitorpoll.h
new file mode 100644
index 000000000..6d086aa6d
--- /dev/null
+++ b/gio/gmemorymonitorpoll.h
@@ -0,0 +1,38 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2025 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see .
+ */
+
+#ifndef __G_MEMORY_MONITOR_POLL_H__
+#define __G_MEMORY_MONITOR_POLL_H__
+
+#include "gmemorymonitorbase.h"
+
+#include
+
+G_BEGIN_DECLS
+
+#define G_TYPE_MEMORY_MONITOR_POLL (g_memory_monitor_poll_get_type ())
+G_DECLARE_FINAL_TYPE (GMemoryMonitorPoll, g_memory_monitor_poll, G, MEMORY_MONITOR_POLL, GMemoryMonitorBase)
+
+GIO_AVAILABLE_IN_2_86
+GType g_memory_monitor_poll_get_type (void);
+
+G_END_DECLS
+
+#endif /* __G_MEMORY_MONITOR_PSI_H__ */
diff --git a/gio/meson.build b/gio/meson.build
index fe442e393..20a22aa0b 100644
--- a/gio/meson.build
+++ b/gio/meson.build
@@ -391,6 +391,12 @@ if host_system != 'windows'
'gunixoutputstream.h',
)
+ if glib_conf.has('HAVE_SYSINFO')
+ unix_sources += files(
+ 'gmemorymonitorpoll.c',
+ )
+ endif
+
if glib_have_cocoa
settings_sources += files('gnextstepsettingsbackend.m')
contenttype_sources += files('gcontenttype-osx.m')