Branch data Line data Source code
1 : : /* Test case for GNOME #662395
2 : : *
3 : : * Copyright (C) 2008-2010 Red Hat, Inc.
4 : : * Copyright (C) 2011 Nokia Corporation
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2.1 of the License, or (at your option) any later version.
12 : : *
13 : : * This library is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General
19 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 : : *
21 : : * Author: Simon McVittie <simon.mcvittie@collabora.co.uk>
22 : : */
23 : :
24 : : #include <config.h>
25 : :
26 : : #include <unistd.h>
27 : : #include <string.h>
28 : :
29 : : #include <gio/gio.h>
30 : :
31 : : #include "test-io-stream.h"
32 : : #include "test-pipe-unix.h"
33 : :
34 : : #define MY_TYPE_OUTPUT_STREAM \
35 : : (my_output_stream_get_type ())
36 : : #define MY_OUTPUT_STREAM(o) \
37 : : (G_TYPE_CHECK_INSTANCE_CAST ((o), \
38 : : MY_TYPE_OUTPUT_STREAM, \
39 : : MyOutputStream))
40 : : #define MY_IS_OUTPUT_STREAM(o) \
41 : : (G_TYPE_CHECK_INSTANCE_TYPE ((o), MY_TYPE_OUTPUT_STREAM))
42 : :
43 : : G_LOCK_DEFINE_STATIC (write);
44 : :
45 : : typedef struct {
46 : : GFilterOutputStream parent;
47 : :
48 : : gint started; /* (atomic) */
49 : : gint finished; /* (atomic) */
50 : : gint flushed; /* (atomic) */
51 : :
52 : : GOutputStream *real_output;
53 : : } MyOutputStream;
54 : :
55 : : typedef struct {
56 : : GFilterOutputStreamClass parent;
57 : : } MyOutputStreamClass;
58 : :
59 : : static GType my_output_stream_get_type (void) G_GNUC_CONST;
60 : :
61 : 229 : G_DEFINE_TYPE (MyOutputStream, my_output_stream, G_TYPE_FILTER_OUTPUT_STREAM)
62 : :
63 : : /* Called from GDBusWorker thread */
64 : : static gssize
65 : 12 : my_output_stream_write (GOutputStream *os,
66 : : const void *buffer,
67 : : gsize count,
68 : : GCancellable *cancellable,
69 : : GError **error)
70 : : {
71 : 12 : MyOutputStream *self = MY_OUTPUT_STREAM (os);
72 : 12 : GFilterOutputStream *filter = G_FILTER_OUTPUT_STREAM (os);
73 : 12 : GOutputStream *real = g_filter_output_stream_get_base_stream (filter);
74 : : gssize ret;
75 : :
76 : 12 : g_atomic_int_add (&self->started, count);
77 : : /* Other threads can make writing block forever by taking this lock */
78 : 12 : G_LOCK (write);
79 : 12 : ret = g_output_stream_write (real, buffer, count, cancellable, error);
80 : 12 : G_UNLOCK (write);
81 : 12 : g_atomic_int_add (&self->finished, count);
82 : 12 : return ret;
83 : : }
84 : :
85 : : /* Called from GDBusWorker thread */
86 : : static gboolean
87 : 6 : my_output_stream_flush (GOutputStream *os,
88 : : GCancellable *cancellable,
89 : : GError **error)
90 : : {
91 : 6 : MyOutputStream *self = MY_OUTPUT_STREAM (os);
92 : 6 : GFilterOutputStream *filter = G_FILTER_OUTPUT_STREAM (os);
93 : 6 : GOutputStream *real = g_filter_output_stream_get_base_stream (filter);
94 : : gint started, finished;
95 : : gboolean ret;
96 : :
97 : : /* These should be equal because you're not allowed to flush with a
98 : : * write pending, and GOutputStream enforces that for its subclasses
99 : : */
100 : 6 : started = g_atomic_int_get (&self->started);
101 : 6 : finished = g_atomic_int_get (&self->finished);
102 : 6 : g_assert_cmpint (started, ==, finished);
103 : :
104 : 6 : ret = g_output_stream_flush (real, cancellable, error);
105 : :
106 : : /* As above, this shouldn't have changed during the flush */
107 : 6 : finished = g_atomic_int_get (&self->finished);
108 : 6 : g_assert_cmpint (started, ==, finished);
109 : :
110 : : /* Checkpoint reached */
111 : 6 : g_atomic_int_set (&self->flushed, finished);
112 : 6 : return ret;
113 : : }
114 : :
115 : : /* Called from any thread; thread-safe */
116 : : static gint
117 : 100 : my_output_stream_get_bytes_started (GOutputStream *os)
118 : : {
119 : 100 : MyOutputStream *self = MY_OUTPUT_STREAM (os);
120 : :
121 : 100 : return g_atomic_int_get (&self->started);
122 : : }
123 : :
124 : : /* Called from any thread; thread-safe */
125 : : static gint
126 : 102 : my_output_stream_get_bytes_finished (GOutputStream *os)
127 : : {
128 : 102 : MyOutputStream *self = MY_OUTPUT_STREAM (os);
129 : :
130 : 102 : return g_atomic_int_get (&self->finished);
131 : : }
132 : :
133 : : /* Called from any thread; thread-safe */
134 : : static gint
135 : 5 : my_output_stream_get_bytes_flushed (GOutputStream *os)
136 : : {
137 : 5 : MyOutputStream *self = MY_OUTPUT_STREAM (os);
138 : :
139 : 5 : return g_atomic_int_get (&self->flushed);
140 : : }
141 : :
142 : : static void
143 : 2 : my_output_stream_init (MyOutputStream *self)
144 : : {
145 : 2 : }
146 : :
147 : : static void
148 : 1 : my_output_stream_class_init (MyOutputStreamClass *cls)
149 : : {
150 : 1 : GOutputStreamClass *ostream_class = (GOutputStreamClass *) cls;
151 : :
152 : 1 : ostream_class->write_fn = my_output_stream_write;
153 : 1 : ostream_class->flush = my_output_stream_flush;
154 : 1 : }
155 : :
156 : : /* ---------------------------------------------------------------------------------------------------- */
157 : :
158 : : typedef struct {
159 : : GError *error;
160 : : gchar *guid;
161 : : gboolean flushed;
162 : :
163 : : GIOStream *client_stream;
164 : : GInputStream *client_istream;
165 : : GOutputStream *client_ostream;
166 : : GOutputStream *client_real_ostream;
167 : : GDBusConnection *client_conn;
168 : :
169 : : GIOStream *server_stream;
170 : : GInputStream *server_istream;
171 : : GOutputStream *server_ostream;
172 : : GDBusConnection *server_conn;
173 : : } Fixture;
174 : :
175 : : static void
176 : 2 : setup_client_cb (GObject *source,
177 : : GAsyncResult *res,
178 : : gpointer user_data)
179 : : {
180 : 2 : Fixture *f = user_data;
181 : :
182 : 2 : f->client_conn = g_dbus_connection_new_finish (res, &f->error);
183 : 2 : g_assert_no_error (f->error);
184 : 2 : g_assert_true (G_IS_DBUS_CONNECTION (f->client_conn));
185 : 2 : g_assert_true (f->client_conn == G_DBUS_CONNECTION (source));
186 : 2 : }
187 : :
188 : : static void
189 : 2 : setup_server_cb (GObject *source,
190 : : GAsyncResult *res,
191 : : gpointer user_data)
192 : : {
193 : 2 : Fixture *f = user_data;
194 : :
195 : 2 : f->server_conn = g_dbus_connection_new_finish (res, &f->error);
196 : 2 : g_assert_no_error (f->error);
197 : 2 : g_assert_true (G_IS_DBUS_CONNECTION (f->server_conn));
198 : 2 : g_assert_true (f->server_conn == G_DBUS_CONNECTION (source));
199 : 2 : }
200 : :
201 : : static void
202 : 2 : setup (Fixture *f,
203 : : gconstpointer test_data G_GNUC_UNUSED)
204 : : {
205 : : gboolean ok;
206 : :
207 : 2 : f->guid = g_dbus_generate_guid ();
208 : :
209 : 2 : ok = test_pipe (&f->server_istream, &f->client_real_ostream, &f->error);
210 : 2 : g_assert_no_error (f->error);
211 : 2 : g_assert_true (G_IS_OUTPUT_STREAM (f->client_real_ostream));
212 : 2 : g_assert_true (G_IS_INPUT_STREAM (f->server_istream));
213 : 2 : g_assert_true (ok);
214 : :
215 : 2 : f->client_ostream = g_object_new (MY_TYPE_OUTPUT_STREAM,
216 : : "base-stream", f->client_real_ostream,
217 : : "close-base-stream", TRUE,
218 : : NULL);
219 : 2 : g_assert_true (G_IS_OUTPUT_STREAM (f->client_ostream));
220 : :
221 : 2 : ok = test_pipe (&f->client_istream, &f->server_ostream, &f->error);
222 : 2 : g_assert_no_error (f->error);
223 : 2 : g_assert_true (G_IS_OUTPUT_STREAM (f->server_ostream));
224 : 2 : g_assert_true (G_IS_INPUT_STREAM (f->client_istream));
225 : 2 : g_assert_true (ok);
226 : :
227 : 2 : f->client_stream = test_io_stream_new (f->client_istream, f->client_ostream);
228 : 2 : f->server_stream = test_io_stream_new (f->server_istream, f->server_ostream);
229 : :
230 : 2 : g_dbus_connection_new (f->client_stream, NULL,
231 : : G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
232 : : NULL, NULL, setup_client_cb, f);
233 : 2 : g_dbus_connection_new (f->server_stream, f->guid,
234 : : G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
235 : : NULL, NULL, setup_server_cb, f);
236 : :
237 : 6 : while (f->client_conn == NULL || f->server_conn == NULL)
238 : 4 : g_main_context_iteration (NULL, TRUE);
239 : 2 : }
240 : :
241 : : static void
242 : 1 : flush_cb (GObject *source,
243 : : GAsyncResult *res,
244 : : gpointer user_data)
245 : : {
246 : 1 : Fixture *f = user_data;
247 : : gboolean ok;
248 : :
249 : 1 : g_assert_true (G_IS_DBUS_CONNECTION (source));
250 : 1 : g_assert_true (G_IS_DBUS_CONNECTION (f->client_conn));
251 : 1 : g_assert_cmpuint ((guintptr) f->client_conn, ==, (guintptr) G_DBUS_CONNECTION (source));
252 : :
253 : 1 : ok = g_dbus_connection_flush_finish (f->client_conn, res, &f->error);
254 : 1 : g_assert_no_error (f->error);
255 : 1 : g_assert_true (ok);
256 : :
257 : 1 : f->flushed = TRUE;
258 : 1 : }
259 : :
260 : : static void
261 : 1 : test_flush_busy (Fixture *f,
262 : : gconstpointer test_data G_GNUC_UNUSED)
263 : : {
264 : : gint initial, started;
265 : : gboolean ok;
266 : :
267 : 1 : initial = my_output_stream_get_bytes_started (f->client_ostream);
268 : : /* make sure the actual write will block */
269 : 1 : G_LOCK (write);
270 : :
271 : 1 : ok = g_dbus_connection_emit_signal (f->client_conn, NULL, "/",
272 : : "com.example.Foo", "SomeSignal", NULL,
273 : : &f->error);
274 : 1 : g_assert_no_error (f->error);
275 : 1 : g_assert_true (ok);
276 : :
277 : : /* wait for at least part of the message to have started writing -
278 : : * the write will block indefinitely in the worker thread
279 : : */
280 : : do {
281 : 99 : started = my_output_stream_get_bytes_started (f->client_ostream);
282 : 99 : g_thread_yield ();
283 : 99 : } while (initial >= started);
284 : :
285 : : /* we haven't flushed anything */
286 : 1 : g_assert_cmpint (my_output_stream_get_bytes_flushed (f->client_ostream),
287 : : <=, initial);
288 : :
289 : : /* start to flush: it can't happen til the write finishes */
290 : 1 : g_dbus_connection_flush (f->client_conn, NULL, flush_cb, f);
291 : :
292 : : /* we still haven't actually flushed anything */
293 : 1 : g_assert_cmpint (my_output_stream_get_bytes_flushed (f->client_ostream),
294 : : <=, initial);
295 : :
296 : : /* let the write finish */
297 : 1 : G_UNLOCK (write);
298 : :
299 : : /* wait for the flush to happen */
300 : 2 : while (!f->flushed)
301 : 1 : g_main_context_iteration (NULL, TRUE);
302 : :
303 : : /* now we have flushed at least what we'd written - but before fixing
304 : : * GNOME#662395 this assertion would fail
305 : : */
306 : 1 : g_assert_cmpint (my_output_stream_get_bytes_flushed (f->client_ostream),
307 : : >=, started);
308 : 1 : }
309 : :
310 : : static void
311 : 1 : test_flush_idle (Fixture *f,
312 : : gconstpointer test_data G_GNUC_UNUSED)
313 : : {
314 : : gint initial, finished;
315 : : gboolean ok;
316 : :
317 : 1 : initial = my_output_stream_get_bytes_finished (f->client_ostream);
318 : :
319 : 1 : ok = g_dbus_connection_emit_signal (f->client_conn, NULL, "/",
320 : : "com.example.Foo", "SomeSignal", NULL,
321 : : &f->error);
322 : 1 : g_assert_no_error (f->error);
323 : 1 : g_assert_true (ok);
324 : :
325 : : /* wait for at least part of the message to have been written */
326 : : do {
327 : 101 : finished = my_output_stream_get_bytes_finished (f->client_ostream);
328 : 101 : g_thread_yield ();
329 : 101 : } while (initial >= finished);
330 : :
331 : : /* we haven't flushed anything */
332 : 1 : g_assert_cmpint (my_output_stream_get_bytes_flushed (f->client_ostream),
333 : : <=, initial);
334 : :
335 : : /* flush with fully-written, but unflushed, messages */
336 : 1 : ok = g_dbus_connection_flush_sync (f->client_conn, NULL, &f->error);
337 : :
338 : : /* now we have flushed at least what we'd written - but before fixing
339 : : * GNOME#662395 this assertion would fail
340 : : */
341 : 1 : g_assert_cmpint (my_output_stream_get_bytes_flushed (f->client_ostream),
342 : : >=, finished);
343 : 1 : }
344 : :
345 : : static void
346 : 2 : teardown (Fixture *f,
347 : : gconstpointer test_data G_GNUC_UNUSED)
348 : : {
349 : 2 : g_clear_error (&f->error);
350 : :
351 : 2 : g_clear_object (&f->client_stream);
352 : 2 : g_clear_object (&f->client_istream);
353 : 2 : g_clear_object (&f->client_ostream);
354 : 2 : g_clear_object (&f->client_real_ostream);
355 : 2 : g_clear_object (&f->client_conn);
356 : :
357 : 2 : g_clear_object (&f->server_stream);
358 : 2 : g_clear_object (&f->server_istream);
359 : 2 : g_clear_object (&f->server_ostream);
360 : 2 : g_clear_object (&f->server_conn);
361 : :
362 : 2 : g_free (f->guid);
363 : 2 : }
364 : :
365 : : /* ---------------------------------------------------------------------------------------------------- */
366 : :
367 : : int
368 : 1 : main (int argc,
369 : : char *argv[])
370 : : {
371 : : gint ret;
372 : :
373 : 1 : g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
374 : :
375 : 1 : g_test_add ("/gdbus/connection/flush/busy", Fixture, NULL,
376 : : setup, test_flush_busy, teardown);
377 : 1 : g_test_add ("/gdbus/connection/flush/idle", Fixture, NULL,
378 : : setup, test_flush_idle, teardown);
379 : :
380 : 1 : ret = g_test_run();
381 : :
382 : 1 : return ret;
383 : : }
|