1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
/*
* Test that demonstrates the GtkCssProvider memory leak in
* indicator_ng_set_label (before fix) and validates the fix.
*
* Before the fix, every call to indicator_ng_set_label() created a new
* GtkCssProvider and added it to the label's GtkStyleContext without
* removing the old one. Since the datetime indicator updates the label
* once per second, this leaked ~2 KB/sec (~170 MB/day).
*
* This test calls set_label in a loop and measures heap growth via
* /proc/self/statm to show the leak is real and the fix works.
*/
#include <gtk/gtk.h>
#include <stdio.h>
static long
get_rss_kb (void)
{
long rss = 0;
FILE *f = fopen ("/proc/self/statm", "r");
if (f) {
long size, resident;
if (fscanf (f, "%ld %ld", &size, &resident) == 2)
rss = resident * (sysconf (_SC_PAGESIZE) / 1024);
fclose (f);
}
return rss;
}
/*
* Simulate what indicator_ng_set_label does using the UNFIXED code:
* creates and adds a new GtkCssProvider every call.
*/
static void
set_label_leaky (GtkLabel *label, guint nPadding)
{
GtkCssProvider *pCssProvider = gtk_css_provider_new ();
GtkStyleContext *pStyleContext = gtk_widget_get_style_context (GTK_WIDGET (label));
gtk_style_context_add_provider (pStyleContext, GTK_STYLE_PROVIDER (pCssProvider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
gchar *sCss = g_strdup_printf ("label{padding-left: %ipx;}", nPadding);
gtk_css_provider_load_from_data (pCssProvider, sCss, -1, NULL);
g_free (sCss);
g_object_unref (pCssProvider);
}
/*
* Simulate what indicator_ng_set_label does AFTER the fix:
* reuse a single provider, only reload its CSS data.
*/
static void
set_label_fixed (GtkLabel *label, guint nPadding, GtkCssProvider **cached)
{
if (*cached == NULL) {
*cached = gtk_css_provider_new ();
GtkStyleContext *pStyleContext = gtk_widget_get_style_context (GTK_WIDGET (label));
gtk_style_context_add_provider (pStyleContext, GTK_STYLE_PROVIDER (*cached),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
gchar *sCss = g_strdup_printf ("label{padding-left: %ipx;}", nPadding);
gtk_css_provider_load_from_data (*cached, sCss, -1, NULL);
g_free (sCss);
}
#define ITERATIONS 100000
static void
test_leaky_set_label (void)
{
GtkWidget *label = gtk_label_new ("12:00:00");
g_object_ref_sink (label);
long rss_before = get_rss_kb ();
for (int i = 0; i < ITERATIONS; i++) {
set_label_leaky (GTK_LABEL (label), 6);
}
long rss_after = get_rss_kb ();
long growth_kb = rss_after - rss_before;
g_test_message ("leaky: RSS before=%ld KB, after=%ld KB, growth=%ld KB over %d iterations",
rss_before, rss_after, growth_kb, ITERATIONS);
/* 100k leaked providers should use at least 50 MB.
If we see significant growth, the leak is confirmed. */
g_assert_cmpint (growth_kb, >, 50000);
g_object_unref (label);
}
static void
test_fixed_set_label (void)
{
GtkWidget *label = gtk_label_new ("12:00:00");
g_object_ref_sink (label);
long rss_before = get_rss_kb ();
GtkCssProvider *cached = NULL;
for (int i = 0; i < ITERATIONS; i++) {
set_label_fixed (GTK_LABEL (label), 6, &cached);
}
long rss_after = get_rss_kb ();
long growth_kb = rss_after - rss_before;
g_test_message ("fixed: RSS before=%ld KB, after=%ld KB, growth=%ld KB over %d iterations",
rss_before, rss_after, growth_kb, ITERATIONS);
/* With the fix, memory growth should be negligible (< 5 MB). */
g_assert_cmpint (growth_kb, <, 5000);
g_object_unref (cached);
g_object_unref (label);
}
int
main (int argc, char **argv)
{
gtk_test_init (&argc, &argv);
g_test_add_func ("/indicator-ng/css-provider-leak/leaky", test_leaky_set_label);
g_test_add_func ("/indicator-ng/css-provider-leak/fixed", test_fixed_set_label);
return g_test_run ();
}
|