1 | #include <glib.h> |
2 | #include <glib/gwakeup.h> |
3 | #ifdef G_OS_UNIX |
4 | #include <unistd.h> |
5 | #endif |
6 | |
7 | #ifdef _WIN32 |
8 | static void alarm (int sec) { } |
9 | #endif |
10 | |
11 | static gboolean |
12 | check_signaled (GWakeup *wakeup) |
13 | { |
14 | GPollFD fd; |
15 | |
16 | g_wakeup_get_pollfd (wakeup, poll_fd: &fd); |
17 | return g_poll (fds: &fd, nfds: 1, timeout: 0); |
18 | } |
19 | |
20 | static void |
21 | wait_for_signaled (GWakeup *wakeup) |
22 | { |
23 | GPollFD fd; |
24 | |
25 | g_wakeup_get_pollfd (wakeup, poll_fd: &fd); |
26 | g_poll (fds: &fd, nfds: 1, timeout: -1); |
27 | } |
28 | |
29 | static void |
30 | test_semantics (void) |
31 | { |
32 | GWakeup *wakeup; |
33 | gint i; |
34 | |
35 | /* prevent the test from deadlocking */ |
36 | alarm (seconds: 60); |
37 | |
38 | wakeup = g_wakeup_new (); |
39 | g_assert (!check_signaled (wakeup)); |
40 | |
41 | g_wakeup_signal (wakeup); |
42 | g_assert (check_signaled (wakeup)); |
43 | |
44 | g_wakeup_acknowledge (wakeup); |
45 | g_assert (!check_signaled (wakeup)); |
46 | |
47 | g_wakeup_free (wakeup); |
48 | |
49 | /* free unused */ |
50 | wakeup = g_wakeup_new (); |
51 | g_wakeup_free (wakeup); |
52 | |
53 | /* free while signaled */ |
54 | wakeup = g_wakeup_new (); |
55 | g_wakeup_signal (wakeup); |
56 | g_wakeup_free (wakeup); |
57 | |
58 | /* ensure excessive signalling doesn't deadlock */ |
59 | wakeup = g_wakeup_new (); |
60 | for (i = 0; i < 1000000; i++) |
61 | g_wakeup_signal (wakeup); |
62 | g_assert (check_signaled (wakeup)); |
63 | |
64 | /* ensure a single acknowledgement is sufficient */ |
65 | g_wakeup_acknowledge (wakeup); |
66 | g_assert (!check_signaled (wakeup)); |
67 | |
68 | g_wakeup_free (wakeup); |
69 | |
70 | /* cancel the alarm */ |
71 | alarm (seconds: 0); |
72 | } |
73 | |
74 | struct token |
75 | { |
76 | gpointer owner; |
77 | gint ttl; |
78 | }; |
79 | |
80 | struct context |
81 | { |
82 | GSList *pending_tokens; |
83 | GMutex lock; |
84 | GWakeup *wakeup; |
85 | gboolean quit; |
86 | }; |
87 | |
88 | #define NUM_THREADS 50 |
89 | #define NUM_TOKENS 5 |
90 | #define TOKEN_TTL 100000 |
91 | |
92 | static struct context contexts[NUM_THREADS]; |
93 | static GThread *threads[NUM_THREADS]; |
94 | static GWakeup *last_token_wakeup; |
95 | static gint tokens_alive; /* (atomic) */ |
96 | |
97 | static void |
98 | context_init (struct context *ctx) |
99 | { |
100 | ctx->pending_tokens = NULL; |
101 | g_mutex_init (mutex: &ctx->lock); |
102 | ctx->wakeup = g_wakeup_new (); |
103 | ctx->quit = FALSE; |
104 | } |
105 | |
106 | static void |
107 | context_clear (struct context *ctx) |
108 | { |
109 | g_assert (ctx->pending_tokens == NULL); |
110 | g_assert (ctx->quit); |
111 | |
112 | g_mutex_clear (mutex: &ctx->lock); |
113 | g_wakeup_free (wakeup: ctx->wakeup); |
114 | } |
115 | |
116 | static void |
117 | context_quit (struct context *ctx) |
118 | { |
119 | g_atomic_int_set (&ctx->quit, TRUE); |
120 | g_wakeup_signal (wakeup: ctx->wakeup); |
121 | } |
122 | |
123 | static struct token * |
124 | context_try_pop_token (struct context *ctx) |
125 | { |
126 | struct token *token = NULL; |
127 | |
128 | g_mutex_lock (mutex: &ctx->lock); |
129 | if (ctx->pending_tokens != NULL) |
130 | { |
131 | token = ctx->pending_tokens->data; |
132 | ctx->pending_tokens = g_slist_delete_link (list: ctx->pending_tokens, |
133 | link_: ctx->pending_tokens); |
134 | } |
135 | g_mutex_unlock (mutex: &ctx->lock); |
136 | |
137 | return token; |
138 | } |
139 | |
140 | static void |
141 | context_push_token (struct context *ctx, |
142 | struct token *token) |
143 | { |
144 | g_assert (token->owner == ctx); |
145 | |
146 | g_mutex_lock (mutex: &ctx->lock); |
147 | ctx->pending_tokens = g_slist_prepend (list: ctx->pending_tokens, data: token); |
148 | g_mutex_unlock (mutex: &ctx->lock); |
149 | |
150 | g_wakeup_signal (wakeup: ctx->wakeup); |
151 | } |
152 | |
153 | static void |
154 | dispatch_token (struct token *token) |
155 | { |
156 | if (token->ttl > 0) |
157 | { |
158 | struct context *ctx; |
159 | gint next_ctx; |
160 | |
161 | next_ctx = g_test_rand_int_range (begin: 0, NUM_THREADS); |
162 | ctx = &contexts[next_ctx]; |
163 | token->owner = ctx; |
164 | token->ttl--; |
165 | |
166 | context_push_token (ctx, token); |
167 | } |
168 | else |
169 | { |
170 | g_slice_free (struct token, token); |
171 | |
172 | if (g_atomic_int_dec_and_test (&tokens_alive)) |
173 | g_wakeup_signal (wakeup: last_token_wakeup); |
174 | } |
175 | } |
176 | |
177 | static struct token * |
178 | token_new (int ttl) |
179 | { |
180 | struct token *token; |
181 | |
182 | token = g_slice_new (struct token); |
183 | token->ttl = ttl; |
184 | |
185 | g_atomic_int_inc (&tokens_alive); |
186 | |
187 | return token; |
188 | } |
189 | |
190 | static gpointer |
191 | thread_func (gpointer data) |
192 | { |
193 | struct context *ctx = data; |
194 | struct token *token; |
195 | |
196 | while (!g_atomic_int_get (&ctx->quit)) |
197 | { |
198 | wait_for_signaled (wakeup: ctx->wakeup); |
199 | g_wakeup_acknowledge (wakeup: ctx->wakeup); |
200 | |
201 | while ((token = context_try_pop_token (ctx)) != NULL) |
202 | { |
203 | g_assert (token->owner == ctx); |
204 | dispatch_token (token); |
205 | } |
206 | } |
207 | |
208 | return NULL; |
209 | } |
210 | |
211 | static void |
212 | test_threaded (void) |
213 | { |
214 | gint i; |
215 | |
216 | /* make sure we don't block forever */ |
217 | alarm (seconds: 60); |
218 | |
219 | /* simple mainloop test based on GWakeup. |
220 | * |
221 | * create a bunch of contexts and a thread to 'run' each one. create |
222 | * some tokens and randomly pass them between the threads, until the |
223 | * TTL on each token is zero. |
224 | * |
225 | * when no tokens are left, signal that we are done. the mainthread |
226 | * will then signal each worker thread to exit and join them to make |
227 | * sure that works. |
228 | */ |
229 | |
230 | last_token_wakeup = g_wakeup_new (); |
231 | |
232 | /* create contexts, assign to threads */ |
233 | for (i = 0; i < NUM_THREADS; i++) |
234 | { |
235 | context_init (ctx: &contexts[i]); |
236 | threads[i] = g_thread_new (name: "test" , func: thread_func, data: &contexts[i]); |
237 | } |
238 | |
239 | /* dispatch tokens */ |
240 | for (i = 0; i < NUM_TOKENS; i++) |
241 | dispatch_token (token: token_new (TOKEN_TTL)); |
242 | |
243 | /* wait until all tokens are gone */ |
244 | wait_for_signaled (wakeup: last_token_wakeup); |
245 | |
246 | /* ask threads to quit, join them, cleanup */ |
247 | for (i = 0; i < NUM_THREADS; i++) |
248 | { |
249 | context_quit (ctx: &contexts[i]); |
250 | g_thread_join (thread: threads[i]); |
251 | context_clear (ctx: &contexts[i]); |
252 | } |
253 | |
254 | g_wakeup_free (wakeup: last_token_wakeup); |
255 | |
256 | /* cancel alarm */ |
257 | alarm (seconds: 0); |
258 | } |
259 | |
260 | int |
261 | main (int argc, char **argv) |
262 | { |
263 | g_test_init (argc: &argc, argv: &argv, NULL); |
264 | |
265 | #ifdef TEST_EVENTFD_FALLBACK |
266 | #define TESTNAME_SUFFIX "-fallback" |
267 | #else |
268 | #define TESTNAME_SUFFIX |
269 | #endif |
270 | |
271 | |
272 | g_test_add_func (testpath: "/gwakeup/semantics" TESTNAME_SUFFIX, test_func: test_semantics); |
273 | g_test_add_func (testpath: "/gwakeup/threaded" TESTNAME_SUFFIX, test_func: test_threaded); |
274 | |
275 | return g_test_run (); |
276 | } |
277 | |