Coverage Report

Created: 2025-09-27 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pango/subprojects/glib/gio/gmemorymonitorpoll.c
Line
Count
Source
1
/* GIO - GLib Input, Output and Streaming Library
2
 *
3
 * Copyright 2025 Red Hat, Inc.
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General
18
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19
 */
20
#include "config.h"
21
22
#include "gcancellable.h"
23
#include "gdbusnamewatching.h"
24
#include "gdbusproxy.h"
25
#include "ginitable.h"
26
#include "gioerror.h"
27
#include "giomodule-priv.h"
28
#include "glib/gstdio.h"
29
#include "glib/glib-private.h"
30
#include "glibintl.h"
31
#include "gmemorymonitor.h"
32
#include "gmemorymonitorpoll.h"
33
34
#include <fcntl.h>
35
#include <unistd.h>
36
37
/**
38
 * GMemoryMonitorPoll:
39
 *
40
 * A [iface@Gio.MemoryMonitor] which polls the system free/used
41
 * memory ratio on a fixed timer.
42
 *
43
 * It polls, for example, every 10 seconds, and emits different
44
 * [signal@Gio.MemoryMonitor::low-memory-warning] signals if it falls below several
45
 * ‘low’ thresholds.
46
 *
47
 * The system free/used memory ratio is queried using [`sysinfo()`](man:sysinfo(2)).
48
 *
49
 * This is intended as a fallback implementation of [iface@Gio.MemoryMonitor] in case
50
 * other, more performant, implementations are not supported on the system.
51
 *
52
 * Since: 2.86
53
 */
54
55
typedef enum {
56
  PROP_MEM_FREE_RATIO = 1,
57
  PROP_POLL_INTERVAL_MS,
58
} GMemoryMonitorPollProperty;
59
60
#define G_MEMORY_MONITOR_POLL_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable))
61
62
static void g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *iface);
63
static void g_memory_monitor_poll_initable_iface_init (GInitableIface *iface);
64
65
struct _GMemoryMonitorPoll
66
{
67
  GMemoryMonitorBase parent_instance;
68
69
  GMainContext *worker;  /* (unowned) */
70
  GSource *source_timeout;  /* (owned) */
71
72
  /* it overrides the default timeout when running the test */
73
  unsigned int poll_interval_ms;  /* zero to use the default */
74
  gdouble mem_free_ratio;
75
};
76
77
/* the default monitor timeout */
78
0
#define G_MEMORY_MONITOR_PSI_DEFAULT_SEC 10
79
80
0
G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorPoll, g_memory_monitor_poll, G_TYPE_MEMORY_MONITOR_BASE,
81
0
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
82
0
                                                g_memory_monitor_poll_initable_iface_init)
83
0
                         G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR,
84
0
                                                g_memory_monitor_poll_iface_init)
85
0
                         _g_io_modules_ensure_extension_points_registered ();
86
0
                         g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME,
87
0
                                                         g_define_type_id,
88
0
                                                         "poll",
89
0
                                                         10))
90
0
91
0
static void
92
0
g_memory_monitor_poll_init (GMemoryMonitorPoll *mem_poll)
93
0
{
94
0
  mem_poll->mem_free_ratio = -1.0;
95
0
}
96
97
static void
98
g_memory_monitor_poll_set_property (GObject      *object,
99
                                    guint         prop_id,
100
                                    const GValue *value,
101
                                    GParamSpec   *pspec)
102
0
{
103
0
  GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
104
105
0
  switch ((GMemoryMonitorPollProperty) prop_id)
106
0
    {
107
0
    case PROP_MEM_FREE_RATIO:
108
0
      monitor->mem_free_ratio = g_value_get_double (value);
109
0
      break;
110
0
    case PROP_POLL_INTERVAL_MS:
111
0
      monitor->poll_interval_ms = g_value_get_uint (value);
112
0
      break;
113
0
    default:
114
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
115
0
      break;
116
0
    }
117
0
}
118
119
static void
120
g_memory_monitor_poll_get_property (GObject    *object,
121
                                    guint       prop_id,
122
                                    GValue     *value,
123
                                    GParamSpec *pspec)
124
0
{
125
0
  GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
126
127
0
  switch ((GMemoryMonitorPollProperty) prop_id)
128
0
    {
129
0
    case PROP_MEM_FREE_RATIO:
130
0
      g_value_set_double (value, monitor->mem_free_ratio);
131
0
      break;
132
0
    case PROP_POLL_INTERVAL_MS:
133
0
      g_value_set_uint (value, monitor->poll_interval_ms);
134
0
      break;
135
0
    default:
136
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137
0
      break;
138
0
    }
139
0
}
140
141
typedef struct
142
{
143
  GWeakRef monitor_weak;  /* (element-type GMemoryMonitorPoll) */
144
} CallbackData;
145
146
static CallbackData *
147
callback_data_new (GMemoryMonitorPoll *monitor)
148
0
{
149
0
  CallbackData *data = g_new0 (CallbackData, 1);
150
0
  g_weak_ref_init (&data->monitor_weak, monitor);
151
0
  return g_steal_pointer (&data);
152
0
}
153
154
static void
155
callback_data_free (CallbackData *data)
156
0
{
157
0
  g_weak_ref_clear (&data->monitor_weak);
158
0
  g_free (data);
159
0
}
160
161
static gboolean
162
g_memory_monitor_mem_ratio_cb (gpointer user_data)
163
0
{
164
0
  CallbackData *data = user_data;
165
0
  GMemoryMonitorPoll *monitor = NULL;
166
0
  gdouble mem_ratio;
167
0
  GMemoryMonitorLowMemoryLevel warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID;
168
169
  /* It’s possible for the dispatch of this callback to race with finalising
170
   * the `GMemoryMonitorPoll`, hence the use of a thread-safe weak ref. */
171
0
  monitor = g_weak_ref_get (&data->monitor_weak);
172
0
  if (monitor == NULL)
173
0
    return G_SOURCE_REMOVE;
174
175
  /* Should be executed in the worker context */
176
0
  g_assert (g_main_context_is_owner (monitor->worker));
177
178
0
  mem_ratio = g_memory_monitor_base_query_mem_ratio ();
179
180
  /* free ratio override */
181
0
  if (monitor->mem_free_ratio >= 0.0)
182
0
    mem_ratio = monitor->mem_free_ratio;
183
184
0
  if (mem_ratio < 0.0)
185
0
    {
186
0
      g_clear_object (&monitor);
187
0
      return G_SOURCE_REMOVE;
188
0
    }
189
190
0
  if (mem_ratio > 0.5)
191
0
    {
192
0
      g_clear_object (&monitor);
193
0
      return G_SOURCE_CONTINUE;
194
0
    }
195
196
0
  g_debug ("memory free ratio %f", mem_ratio);
197
198
0
  if (mem_ratio < 0.2)
199
0
    warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_CRITICAL;
200
0
  else if (mem_ratio < 0.3)
201
0
    warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_MEDIUM;
202
0
  else if (mem_ratio < 0.4)
203
0
    warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_LOW;
204
205
0
  if (warning_level != G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID)
206
0
    g_memory_monitor_base_send_event_to_user (G_MEMORY_MONITOR_BASE (monitor), warning_level);
207
208
0
  g_clear_object (&monitor);
209
210
0
  return G_SOURCE_CONTINUE;
211
0
}
212
213
static gboolean
214
g_memory_monitor_poll_initable_init (GInitable     *initable,
215
                                     GCancellable  *cancellable,
216
                                     GError       **error)
217
0
{
218
0
  GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (initable);
219
0
  GSource *source;
220
221
0
  if (monitor->poll_interval_ms > 0)
222
0
    {
223
0
      if (monitor->poll_interval_ms < G_TIME_SPAN_MILLISECOND)
224
0
        source = g_timeout_source_new (monitor->poll_interval_ms);
225
0
      else
226
0
        source = g_timeout_source_new_seconds (monitor->poll_interval_ms / G_TIME_SPAN_MILLISECOND);
227
0
    }
228
0
  else
229
0
    {
230
      /* default 10 second */
231
0
      source = g_timeout_source_new_seconds (G_MEMORY_MONITOR_PSI_DEFAULT_SEC);
232
0
    }
233
234
0
  g_source_set_callback (source, g_memory_monitor_mem_ratio_cb,
235
0
                         callback_data_new (monitor), (GDestroyNotify) callback_data_free);
236
0
  monitor->worker = GLIB_PRIVATE_CALL (g_get_worker_context) ();
237
0
  g_source_attach (source, monitor->worker);
238
0
  monitor->source_timeout = g_steal_pointer (&source);
239
240
0
  return TRUE;
241
0
}
242
243
static void
244
g_memory_monitor_poll_finalize (GObject *object)
245
0
{
246
0
  GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
247
248
0
  g_source_destroy (monitor->source_timeout);
249
0
  g_source_unref (monitor->source_timeout);
250
251
0
  G_OBJECT_CLASS (g_memory_monitor_poll_parent_class)->finalize (object);
252
0
}
253
254
static void
255
g_memory_monitor_poll_class_init (GMemoryMonitorPollClass *klass)
256
0
{
257
0
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
258
259
0
  object_class->set_property = g_memory_monitor_poll_set_property;
260
0
  object_class->get_property = g_memory_monitor_poll_get_property;
261
0
  object_class->finalize = g_memory_monitor_poll_finalize;
262
263
  /**
264
   * GMemoryMonitorPoll:mem-free-ratio:
265
   *
266
   * Override the memory free ratio
267
   *
268
   * Since: 2.86
269
   */
270
0
  g_object_class_install_property (object_class,
271
0
                                   PROP_MEM_FREE_RATIO,
272
0
                                   g_param_spec_double ("mem-free-ratio", NULL, NULL,
273
0
                                                        -1.0, 1.0,
274
0
                                                        -1.0,
275
0
                                                        G_PARAM_READWRITE |
276
0
                                                        G_PARAM_CONSTRUCT_ONLY |
277
0
                                                        G_PARAM_STATIC_STRINGS));
278
279
  /**
280
   * GMemoryMonitorPoll:poll-interval-ms:
281
   *
282
   * Override the poll interval for monitoring the memory usage.
283
   *
284
   * The interval is in milliseconds. Zero means to use the default interval.
285
   *
286
   * Since: 2.86
287
   */
288
0
  g_object_class_install_property (object_class,
289
0
                                   PROP_POLL_INTERVAL_MS,
290
0
                                   g_param_spec_uint ("poll-interval-ms", NULL, NULL,
291
0
                                                      0, G_MAXUINT,
292
0
                                                      0,
293
0
                                                      G_PARAM_READWRITE |
294
0
                                                      G_PARAM_CONSTRUCT_ONLY |
295
0
                                                      G_PARAM_STATIC_STRINGS));
296
297
0
}
298
299
static void
300
g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *monitor_iface)
301
0
{
302
0
}
303
304
static void
305
g_memory_monitor_poll_initable_iface_init (GInitableIface *iface)
306
0
{
307
0
  iface->init = g_memory_monitor_poll_initable_init;
308
0
}
309