1 | |
2 | /* Unit tests for GOnce and friends |
3 | * Copyright (C) 2011 Red Hat, Inc |
4 | * Author: Matthias Clasen |
5 | * |
6 | * This work is provided "as is"; redistribution and modification |
7 | * in whole or in part, in any medium, physical or electronic is |
8 | * permitted without restriction. |
9 | * |
10 | * This work is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
13 | * |
14 | * In no event shall the authors or contributors be liable for any |
15 | * direct, indirect, incidental, special, exemplary, or consequential |
16 | * damages (including, but not limited to, procurement of substitute |
17 | * goods or services; loss of use, data, or profits; or business |
18 | * interruption) however caused and on any theory of liability, whether |
19 | * in contract, strict liability, or tort (including negligence or |
20 | * otherwise) arising in any way out of the use of this software, even |
21 | * if advised of the possibility of such damage. |
22 | */ |
23 | |
24 | #include <glib.h> |
25 | |
26 | #if GLIB_SIZEOF_VOID_P > 4 |
27 | #define THREADS 1000 |
28 | #else |
29 | #define THREADS 100 |
30 | #endif |
31 | |
32 | static gpointer |
33 | do_once (gpointer data) |
34 | { |
35 | static gint i = 0; |
36 | |
37 | i++; |
38 | |
39 | return GINT_TO_POINTER (i); |
40 | } |
41 | |
42 | static void |
43 | test_once_single_threaded (void) |
44 | { |
45 | GOnce once = G_ONCE_INIT; |
46 | gpointer res; |
47 | |
48 | g_test_summary (summary: "Test g_once() usage from a single thread" ); |
49 | |
50 | g_assert (once.status == G_ONCE_STATUS_NOTCALLED); |
51 | |
52 | res = g_once (&once, do_once, NULL); |
53 | g_assert_cmpint (GPOINTER_TO_INT (res), ==, 1); |
54 | |
55 | g_assert (once.status == G_ONCE_STATUS_READY); |
56 | |
57 | res = g_once (&once, do_once, NULL); |
58 | g_assert_cmpint (GPOINTER_TO_INT (res), ==, 1); |
59 | } |
60 | |
61 | static GOnce once_multi_threaded = G_ONCE_INIT; |
62 | static gint once_multi_threaded_counter = 0; |
63 | static GCond once_multi_threaded_cond; |
64 | static GMutex once_multi_threaded_mutex; |
65 | static guint once_multi_threaded_n_threads_waiting = 0; |
66 | |
67 | static gpointer |
68 | do_once_multi_threaded (gpointer data) |
69 | { |
70 | gint old_value; |
71 | |
72 | /* While this function should only ever be executed once, by one thread, |
73 | * we should use atomics to ensure that if there were a bug, writes to |
74 | * `once_multi_threaded_counter` from multiple threads would not get lost and |
75 | * mean the test erroneously succeeded. */ |
76 | old_value = g_atomic_int_add (&once_multi_threaded_counter, 1); |
77 | |
78 | return GINT_TO_POINTER (old_value + 1); |
79 | } |
80 | |
81 | static gpointer |
82 | once_thread_func (gpointer data) |
83 | { |
84 | gpointer res; |
85 | guint n_threads_expected = GPOINTER_TO_UINT (data); |
86 | |
87 | /* Don’t immediately call g_once(), otherwise the first thread to be created |
88 | * will end up calling the once-function, and there will be very little |
89 | * contention. */ |
90 | g_mutex_lock (mutex: &once_multi_threaded_mutex); |
91 | |
92 | once_multi_threaded_n_threads_waiting++; |
93 | g_cond_broadcast (cond: &once_multi_threaded_cond); |
94 | |
95 | while (once_multi_threaded_n_threads_waiting < n_threads_expected) |
96 | g_cond_wait (cond: &once_multi_threaded_cond, mutex: &once_multi_threaded_mutex); |
97 | g_mutex_unlock (mutex: &once_multi_threaded_mutex); |
98 | |
99 | /* Actually run the test. */ |
100 | res = g_once (&once_multi_threaded, do_once_multi_threaded, NULL); |
101 | g_assert_cmpint (GPOINTER_TO_INT (res), ==, 1); |
102 | |
103 | return NULL; |
104 | } |
105 | |
106 | static void |
107 | test_once_multi_threaded (void) |
108 | { |
109 | guint i; |
110 | GThread *threads[THREADS]; |
111 | |
112 | g_test_summary (summary: "Test g_once() usage from multiple threads" ); |
113 | |
114 | for (i = 0; i < G_N_ELEMENTS (threads); i++) |
115 | threads[i] = g_thread_new (name: "once-multi-threaded" , |
116 | func: once_thread_func, |
117 | GUINT_TO_POINTER (G_N_ELEMENTS (threads))); |
118 | |
119 | /* All threads have started up, so start the test. */ |
120 | g_cond_broadcast (cond: &once_multi_threaded_cond); |
121 | |
122 | for (i = 0; i < G_N_ELEMENTS (threads); i++) |
123 | g_thread_join (thread: threads[i]); |
124 | |
125 | g_assert_cmpint (g_atomic_int_get (&once_multi_threaded_counter), ==, 1); |
126 | } |
127 | |
128 | static void |
129 | test_once_init_single_threaded (void) |
130 | { |
131 | static gsize init = 0; |
132 | |
133 | g_test_summary (summary: "Test g_once_init_{enter,leave}() usage from a single thread" ); |
134 | |
135 | if (g_once_init_enter (&init)) |
136 | { |
137 | g_assert (TRUE); |
138 | g_once_init_leave (&init, 1); |
139 | } |
140 | |
141 | g_assert_cmpint (init, ==, 1); |
142 | if (g_once_init_enter (&init)) |
143 | { |
144 | g_assert_not_reached (); |
145 | g_once_init_leave (&init, 2); |
146 | } |
147 | g_assert_cmpint (init, ==, 1); |
148 | } |
149 | |
150 | static gint64 shared; |
151 | |
152 | static void |
153 | init_shared (void) |
154 | { |
155 | static gsize init = 0; |
156 | |
157 | if (g_once_init_enter (&init)) |
158 | { |
159 | shared += 42; |
160 | |
161 | g_once_init_leave (&init, 1); |
162 | } |
163 | } |
164 | |
165 | static gpointer |
166 | thread_func (gpointer data) |
167 | { |
168 | init_shared (); |
169 | |
170 | return NULL; |
171 | } |
172 | |
173 | static void |
174 | test_once_init_multi_threaded (void) |
175 | { |
176 | gsize i; |
177 | GThread *threads[THREADS]; |
178 | |
179 | g_test_summary (summary: "Test g_once_init_{enter,leave}() usage from multiple threads" ); |
180 | |
181 | shared = 0; |
182 | |
183 | for (i = 0; i < G_N_ELEMENTS (threads); i++) |
184 | threads[i] = g_thread_new (name: "once-init-multi-threaded" , func: thread_func, NULL); |
185 | |
186 | for (i = 0; i < G_N_ELEMENTS (threads); i++) |
187 | g_thread_join (thread: threads[i]); |
188 | |
189 | g_assert_cmpint (shared, ==, 42); |
190 | } |
191 | |
192 | static void |
193 | test_once_init_string (void) |
194 | { |
195 | static const gchar *val; |
196 | |
197 | g_test_summary (summary: "Test g_once_init_{enter,leave}() usage with a string" ); |
198 | |
199 | if (g_once_init_enter (&val)) |
200 | g_once_init_leave (&val, "foo" ); |
201 | |
202 | g_assert_cmpstr (val, ==, "foo" ); |
203 | } |
204 | |
205 | int |
206 | main (int argc, char *argv[]) |
207 | { |
208 | g_test_init (argc: &argc, argv: &argv, NULL); |
209 | |
210 | g_test_add_func (testpath: "/once/single-threaded" , test_func: test_once_single_threaded); |
211 | g_test_add_func (testpath: "/once/multi-threaded" , test_func: test_once_multi_threaded); |
212 | g_test_add_func (testpath: "/once-init/single-threaded" , test_func: test_once_init_single_threaded); |
213 | g_test_add_func (testpath: "/once-init/multi-threaded" , test_func: test_once_init_multi_threaded); |
214 | g_test_add_func (testpath: "/once-init/string" , test_func: test_once_init_string); |
215 | |
216 | return g_test_run (); |
217 | } |
218 | |