1 | /* |
2 | * Copyright (C) 2011 Red Hat, Inc. |
3 | * |
4 | * This work is provided "as is"; redistribution and modification |
5 | * in whole or in part, in any medium, physical or electronic is |
6 | * permitted without restriction. |
7 | * |
8 | * This work is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
11 | * |
12 | * In no event shall the authors or contributors be liable for any |
13 | * direct, indirect, incidental, special, exemplary, or consequential |
14 | * damages (including, but not limited to, procurement of substitute |
15 | * goods or services; loss of use, data, or profits; or business |
16 | * interruption) however caused and on any theory of liability, whether |
17 | * in contract, strict liability, or tort (including negligence or |
18 | * otherwise) arising in any way out of the use of this software, even |
19 | * if advised of the possibility of such damage. |
20 | * |
21 | * Author: Colin Walters <walters@verbum.org> |
22 | */ |
23 | |
24 | #include "config.h" |
25 | |
26 | #include <glib.h> |
27 | #include <string.h> |
28 | |
29 | static char *echo_prog_path; |
30 | |
31 | static void |
32 | multithreaded_test_run (GThreadFunc function) |
33 | { |
34 | guint i; |
35 | GPtrArray *threads = g_ptr_array_new (); |
36 | guint n_threads; |
37 | |
38 | /* Limit to 64, otherwise we may hit file descriptor limits and such */ |
39 | n_threads = MIN (g_get_num_processors () * 2, 64); |
40 | |
41 | for (i = 0; i < n_threads; i++) |
42 | { |
43 | GThread *thread; |
44 | |
45 | thread = g_thread_new (name: "test" , func: function, GUINT_TO_POINTER (i)); |
46 | g_ptr_array_add (array: threads, data: thread); |
47 | } |
48 | |
49 | for (i = 0; i < n_threads; i++) |
50 | { |
51 | gpointer ret; |
52 | ret = g_thread_join (g_ptr_array_index (threads, i)); |
53 | g_assert_cmpint (GPOINTER_TO_UINT (ret), ==, i); |
54 | } |
55 | g_ptr_array_free (array: threads, TRUE); |
56 | } |
57 | |
58 | static gpointer |
59 | test_spawn_sync_multithreaded_instance (gpointer data) |
60 | { |
61 | guint tnum = GPOINTER_TO_UINT (data); |
62 | GError *error = NULL; |
63 | GPtrArray *argv; |
64 | char *arg; |
65 | char *stdout_str; |
66 | int estatus; |
67 | |
68 | arg = g_strdup_printf (format: "thread %u" , tnum); |
69 | |
70 | argv = g_ptr_array_new (); |
71 | g_ptr_array_add (array: argv, data: echo_prog_path); |
72 | g_ptr_array_add (array: argv, data: arg); |
73 | g_ptr_array_add (array: argv, NULL); |
74 | |
75 | g_spawn_sync (NULL, argv: (char**)argv->pdata, NULL, flags: G_SPAWN_DEFAULT, NULL, NULL, standard_output: &stdout_str, NULL, exit_status: &estatus, error: &error); |
76 | g_assert_no_error (error); |
77 | g_assert_cmpstr (arg, ==, stdout_str); |
78 | g_free (mem: arg); |
79 | g_free (mem: stdout_str); |
80 | g_ptr_array_free (array: argv, TRUE); |
81 | |
82 | return GUINT_TO_POINTER (tnum); |
83 | } |
84 | |
85 | static void |
86 | test_spawn_sync_multithreaded (void) |
87 | { |
88 | multithreaded_test_run (function: test_spawn_sync_multithreaded_instance); |
89 | } |
90 | |
91 | typedef struct { |
92 | GMainLoop *loop; |
93 | gboolean child_exited; |
94 | gboolean stdout_done; |
95 | GString *stdout_buf; |
96 | } SpawnAsyncMultithreadedData; |
97 | |
98 | static gboolean |
99 | on_child_exited (GPid pid, |
100 | gint status, |
101 | gpointer datap) |
102 | { |
103 | SpawnAsyncMultithreadedData *data = datap; |
104 | |
105 | data->child_exited = TRUE; |
106 | if (data->child_exited && data->stdout_done) |
107 | g_main_loop_quit (loop: data->loop); |
108 | |
109 | return G_SOURCE_REMOVE; |
110 | } |
111 | |
112 | static gboolean |
113 | on_child_stdout (GIOChannel *channel, |
114 | GIOCondition condition, |
115 | gpointer datap) |
116 | { |
117 | char buf[1024]; |
118 | GError *error = NULL; |
119 | gsize bytes_read; |
120 | GIOStatus status; |
121 | SpawnAsyncMultithreadedData *data = datap; |
122 | |
123 | read: |
124 | status = g_io_channel_read_chars (channel, buf, count: sizeof (buf), bytes_read: &bytes_read, error: &error); |
125 | if (status == G_IO_STATUS_NORMAL) |
126 | { |
127 | g_string_append_len (string: data->stdout_buf, val: buf, len: (gssize) bytes_read); |
128 | if (bytes_read == sizeof (buf)) |
129 | goto read; |
130 | } |
131 | else if (status == G_IO_STATUS_EOF) |
132 | { |
133 | g_string_append_len (string: data->stdout_buf, val: buf, len: (gssize) bytes_read); |
134 | data->stdout_done = TRUE; |
135 | } |
136 | else if (status == G_IO_STATUS_ERROR) |
137 | { |
138 | g_error ("Error reading from child stdin: %s" , error->message); |
139 | } |
140 | |
141 | if (data->child_exited && data->stdout_done) |
142 | g_main_loop_quit (loop: data->loop); |
143 | |
144 | return !data->stdout_done; |
145 | } |
146 | |
147 | static gpointer |
148 | test_spawn_async_multithreaded_instance (gpointer thread_data) |
149 | { |
150 | guint tnum = GPOINTER_TO_UINT (thread_data); |
151 | GError *error = NULL; |
152 | GPtrArray *argv; |
153 | char *arg; |
154 | GPid pid; |
155 | GMainContext *context; |
156 | GMainLoop *loop; |
157 | GIOChannel *channel; |
158 | GSource *source; |
159 | int child_stdout_fd; |
160 | SpawnAsyncMultithreadedData data; |
161 | |
162 | context = g_main_context_new (); |
163 | loop = g_main_loop_new (context, TRUE); |
164 | |
165 | arg = g_strdup_printf (format: "thread %u" , tnum); |
166 | |
167 | argv = g_ptr_array_new (); |
168 | g_ptr_array_add (array: argv, data: echo_prog_path); |
169 | g_ptr_array_add (array: argv, data: arg); |
170 | g_ptr_array_add (array: argv, NULL); |
171 | |
172 | g_spawn_async_with_pipes (NULL, argv: (char**)argv->pdata, NULL, flags: G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, child_pid: &pid, NULL, |
173 | standard_output: &child_stdout_fd, NULL, error: &error); |
174 | g_assert_no_error (error); |
175 | g_ptr_array_free (array: argv, TRUE); |
176 | |
177 | data.loop = loop; |
178 | data.stdout_done = FALSE; |
179 | data.child_exited = FALSE; |
180 | data.stdout_buf = g_string_new (init: 0); |
181 | |
182 | source = g_child_watch_source_new (pid); |
183 | g_source_set_callback (source, func: (GSourceFunc)on_child_exited, data: &data, NULL); |
184 | g_source_attach (source, context); |
185 | g_source_unref (source); |
186 | |
187 | channel = g_io_channel_unix_new (fd: child_stdout_fd); |
188 | source = g_io_create_watch (channel, condition: G_IO_IN | G_IO_HUP); |
189 | g_source_set_callback (source, func: (GSourceFunc)on_child_stdout, data: &data, NULL); |
190 | g_source_attach (source, context); |
191 | g_source_unref (source); |
192 | |
193 | g_main_loop_run (loop); |
194 | |
195 | g_assert (data.child_exited); |
196 | g_assert (data.stdout_done); |
197 | g_assert_cmpstr (data.stdout_buf->str, ==, arg); |
198 | g_string_free (string: data.stdout_buf, TRUE); |
199 | |
200 | g_io_channel_unref (channel); |
201 | g_main_context_unref (context); |
202 | g_main_loop_unref (loop); |
203 | |
204 | g_free (mem: arg); |
205 | |
206 | return GUINT_TO_POINTER (tnum); |
207 | } |
208 | |
209 | static void |
210 | test_spawn_async_multithreaded (void) |
211 | { |
212 | multithreaded_test_run (function: test_spawn_async_multithreaded_instance); |
213 | } |
214 | |
215 | int |
216 | main (int argc, |
217 | char *argv[]) |
218 | { |
219 | char *dirname; |
220 | int ret; |
221 | |
222 | g_test_init (argc: &argc, argv: &argv, NULL); |
223 | |
224 | dirname = g_path_get_dirname (file_name: argv[0]); |
225 | echo_prog_path = g_build_filename (first_element: dirname, "test-spawn-echo" EXEEXT, NULL); |
226 | if (!g_file_test (filename: echo_prog_path, test: G_FILE_TEST_EXISTS)) |
227 | { |
228 | g_free (mem: echo_prog_path); |
229 | echo_prog_path = g_build_filename (first_element: dirname, "lt-test-spawn-echo" EXEEXT, NULL); |
230 | } |
231 | g_free (mem: dirname); |
232 | |
233 | g_assert (g_file_test (echo_prog_path, G_FILE_TEST_EXISTS)); |
234 | |
235 | g_test_add_func (testpath: "/gthread/spawn-sync" , test_func: test_spawn_sync_multithreaded); |
236 | g_test_add_func (testpath: "/gthread/spawn-async" , test_func: test_spawn_async_multithreaded); |
237 | |
238 | ret = g_test_run(); |
239 | |
240 | g_free (mem: echo_prog_path); |
241 | |
242 | return ret; |
243 | } |
244 | |