1 | /* GIO - GLib Input, Output and Streaming Library |
2 | * |
3 | * Copyright (C) 2006-2007 Red Hat, Inc. |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2.1 of the License, or (at your option) any later version. |
9 | * |
10 | * This library 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. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General |
16 | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
17 | * |
18 | * Author: Alexander Larsson <alexl@redhat.com> |
19 | */ |
20 | |
21 | #include "config.h" |
22 | |
23 | #include "gioscheduler.h" |
24 | #include "gcancellable.h" |
25 | #include "gtask.h" |
26 | |
27 | /** |
28 | * SECTION:gioscheduler |
29 | * @short_description: I/O Scheduler |
30 | * @include: gio/gio.h |
31 | * |
32 | * As of GLib 2.36, #GIOScheduler is deprecated in favor of |
33 | * #GThreadPool and #GTask. |
34 | * |
35 | * Schedules asynchronous I/O operations. #GIOScheduler integrates |
36 | * into the main event loop (#GMainLoop) and uses threads. |
37 | */ |
38 | |
39 | struct _GIOSchedulerJob { |
40 | GList *active_link; |
41 | GTask *task; |
42 | |
43 | GIOSchedulerJobFunc job_func; |
44 | gpointer data; |
45 | GDestroyNotify destroy_notify; |
46 | |
47 | GCancellable *cancellable; |
48 | gulong cancellable_id; |
49 | GMainContext *context; |
50 | }; |
51 | |
52 | G_LOCK_DEFINE_STATIC(active_jobs); |
53 | static GList *active_jobs = NULL; |
54 | |
55 | static void |
56 | g_io_job_free (GIOSchedulerJob *job) |
57 | { |
58 | if (job->destroy_notify) |
59 | job->destroy_notify (job->data); |
60 | |
61 | G_LOCK (active_jobs); |
62 | active_jobs = g_list_delete_link (list: active_jobs, link_: job->active_link); |
63 | G_UNLOCK (active_jobs); |
64 | |
65 | if (job->cancellable) |
66 | g_object_unref (object: job->cancellable); |
67 | g_main_context_unref (context: job->context); |
68 | g_slice_free (GIOSchedulerJob, job); |
69 | } |
70 | |
71 | static void |
72 | io_job_thread (GTask *task, |
73 | gpointer source_object, |
74 | gpointer task_data, |
75 | GCancellable *cancellable) |
76 | { |
77 | GIOSchedulerJob *job = task_data; |
78 | gboolean result; |
79 | |
80 | if (job->cancellable) |
81 | g_cancellable_push_current (cancellable: job->cancellable); |
82 | |
83 | do |
84 | { |
85 | result = job->job_func (job, job->cancellable, job->data); |
86 | } |
87 | while (result); |
88 | |
89 | if (job->cancellable) |
90 | g_cancellable_pop_current (cancellable: job->cancellable); |
91 | } |
92 | |
93 | /** |
94 | * g_io_scheduler_push_job: |
95 | * @job_func: a #GIOSchedulerJobFunc. |
96 | * @user_data: data to pass to @job_func |
97 | * @notify: (nullable): a #GDestroyNotify for @user_data, or %NULL |
98 | * @io_priority: the [I/O priority][io-priority] |
99 | * of the request. |
100 | * @cancellable: optional #GCancellable object, %NULL to ignore. |
101 | * |
102 | * Schedules the I/O job to run in another thread. |
103 | * |
104 | * @notify will be called on @user_data after @job_func has returned, |
105 | * regardless whether the job was cancelled or has run to completion. |
106 | * |
107 | * If @cancellable is not %NULL, it can be used to cancel the I/O job |
108 | * by calling g_cancellable_cancel() or by calling |
109 | * g_io_scheduler_cancel_all_jobs(). |
110 | * |
111 | * Deprecated: use #GThreadPool or g_task_run_in_thread() |
112 | **/ |
113 | void |
114 | g_io_scheduler_push_job (GIOSchedulerJobFunc job_func, |
115 | gpointer user_data, |
116 | GDestroyNotify notify, |
117 | gint io_priority, |
118 | GCancellable *cancellable) |
119 | { |
120 | GIOSchedulerJob *job; |
121 | GTask *task; |
122 | |
123 | g_return_if_fail (job_func != NULL); |
124 | |
125 | job = g_slice_new0 (GIOSchedulerJob); |
126 | job->job_func = job_func; |
127 | job->data = user_data; |
128 | job->destroy_notify = notify; |
129 | |
130 | if (cancellable) |
131 | job->cancellable = g_object_ref (cancellable); |
132 | |
133 | job->context = g_main_context_ref_thread_default (); |
134 | |
135 | G_LOCK (active_jobs); |
136 | active_jobs = g_list_prepend (list: active_jobs, data: job); |
137 | job->active_link = active_jobs; |
138 | G_UNLOCK (active_jobs); |
139 | |
140 | task = g_task_new (NULL, cancellable, NULL, NULL); |
141 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
142 | g_task_set_source_tag (task, g_io_scheduler_push_job); |
143 | G_GNUC_END_IGNORE_DEPRECATIONS |
144 | g_task_set_task_data (task, task_data: job, task_data_destroy: (GDestroyNotify)g_io_job_free); |
145 | g_task_set_priority (task, priority: io_priority); |
146 | g_task_run_in_thread (task, task_func: io_job_thread); |
147 | g_object_unref (object: task); |
148 | } |
149 | |
150 | /** |
151 | * g_io_scheduler_cancel_all_jobs: |
152 | * |
153 | * Cancels all cancellable I/O jobs. |
154 | * |
155 | * A job is cancellable if a #GCancellable was passed into |
156 | * g_io_scheduler_push_job(). |
157 | * |
158 | * Deprecated: You should never call this function, since you don't |
159 | * know how other libraries in your program might be making use of |
160 | * gioscheduler. |
161 | **/ |
162 | void |
163 | g_io_scheduler_cancel_all_jobs (void) |
164 | { |
165 | GList *cancellable_list, *l; |
166 | |
167 | G_LOCK (active_jobs); |
168 | cancellable_list = NULL; |
169 | for (l = active_jobs; l != NULL; l = l->next) |
170 | { |
171 | GIOSchedulerJob *job = l->data; |
172 | if (job->cancellable) |
173 | cancellable_list = g_list_prepend (list: cancellable_list, |
174 | g_object_ref (job->cancellable)); |
175 | } |
176 | G_UNLOCK (active_jobs); |
177 | |
178 | for (l = cancellable_list; l != NULL; l = l->next) |
179 | { |
180 | GCancellable *c = l->data; |
181 | g_cancellable_cancel (cancellable: c); |
182 | g_object_unref (object: c); |
183 | } |
184 | g_list_free (list: cancellable_list); |
185 | } |
186 | |
187 | typedef struct { |
188 | GSourceFunc func; |
189 | gboolean ret_val; |
190 | gpointer data; |
191 | GDestroyNotify notify; |
192 | |
193 | GMutex ack_lock; |
194 | GCond ack_condition; |
195 | gboolean ack; |
196 | } MainLoopProxy; |
197 | |
198 | static gboolean |
199 | mainloop_proxy_func (gpointer data) |
200 | { |
201 | MainLoopProxy *proxy = data; |
202 | |
203 | proxy->ret_val = proxy->func (proxy->data); |
204 | |
205 | if (proxy->notify) |
206 | proxy->notify (proxy->data); |
207 | |
208 | g_mutex_lock (mutex: &proxy->ack_lock); |
209 | proxy->ack = TRUE; |
210 | g_cond_signal (cond: &proxy->ack_condition); |
211 | g_mutex_unlock (mutex: &proxy->ack_lock); |
212 | |
213 | return FALSE; |
214 | } |
215 | |
216 | static void |
217 | mainloop_proxy_free (MainLoopProxy *proxy) |
218 | { |
219 | g_mutex_clear (mutex: &proxy->ack_lock); |
220 | g_cond_clear (cond: &proxy->ack_condition); |
221 | g_free (mem: proxy); |
222 | } |
223 | |
224 | /** |
225 | * g_io_scheduler_job_send_to_mainloop: |
226 | * @job: a #GIOSchedulerJob |
227 | * @func: a #GSourceFunc callback that will be called in the original thread |
228 | * @user_data: data to pass to @func |
229 | * @notify: (nullable): a #GDestroyNotify for @user_data, or %NULL |
230 | * |
231 | * Used from an I/O job to send a callback to be run in the thread |
232 | * that the job was started from, waiting for the result (and thus |
233 | * blocking the I/O job). |
234 | * |
235 | * Returns: The return value of @func |
236 | * |
237 | * Deprecated: Use g_main_context_invoke(). |
238 | **/ |
239 | gboolean |
240 | g_io_scheduler_job_send_to_mainloop (GIOSchedulerJob *job, |
241 | GSourceFunc func, |
242 | gpointer user_data, |
243 | GDestroyNotify notify) |
244 | { |
245 | GSource *source; |
246 | MainLoopProxy *proxy; |
247 | gboolean ret_val; |
248 | |
249 | g_return_val_if_fail (job != NULL, FALSE); |
250 | g_return_val_if_fail (func != NULL, FALSE); |
251 | |
252 | proxy = g_new0 (MainLoopProxy, 1); |
253 | proxy->func = func; |
254 | proxy->data = user_data; |
255 | proxy->notify = notify; |
256 | g_mutex_init (mutex: &proxy->ack_lock); |
257 | g_cond_init (cond: &proxy->ack_condition); |
258 | g_mutex_lock (mutex: &proxy->ack_lock); |
259 | |
260 | source = g_idle_source_new (); |
261 | g_source_set_priority (source, G_PRIORITY_DEFAULT); |
262 | g_source_set_callback (source, func: mainloop_proxy_func, data: proxy, |
263 | NULL); |
264 | g_source_set_name (source, name: "[gio] mainloop_proxy_func" ); |
265 | |
266 | g_source_attach (source, context: job->context); |
267 | g_source_unref (source); |
268 | |
269 | while (!proxy->ack) |
270 | g_cond_wait (cond: &proxy->ack_condition, mutex: &proxy->ack_lock); |
271 | g_mutex_unlock (mutex: &proxy->ack_lock); |
272 | |
273 | ret_val = proxy->ret_val; |
274 | mainloop_proxy_free (proxy); |
275 | |
276 | return ret_val; |
277 | } |
278 | |
279 | /** |
280 | * g_io_scheduler_job_send_to_mainloop_async: |
281 | * @job: a #GIOSchedulerJob |
282 | * @func: a #GSourceFunc callback that will be called in the original thread |
283 | * @user_data: data to pass to @func |
284 | * @notify: (nullable): a #GDestroyNotify for @user_data, or %NULL |
285 | * |
286 | * Used from an I/O job to send a callback to be run asynchronously in |
287 | * the thread that the job was started from. The callback will be run |
288 | * when the main loop is available, but at that time the I/O job might |
289 | * have finished. The return value from the callback is ignored. |
290 | * |
291 | * Note that if you are passing the @user_data from g_io_scheduler_push_job() |
292 | * on to this function you have to ensure that it is not freed before |
293 | * @func is called, either by passing %NULL as @notify to |
294 | * g_io_scheduler_push_job() or by using refcounting for @user_data. |
295 | * |
296 | * Deprecated: Use g_main_context_invoke(). |
297 | **/ |
298 | void |
299 | g_io_scheduler_job_send_to_mainloop_async (GIOSchedulerJob *job, |
300 | GSourceFunc func, |
301 | gpointer user_data, |
302 | GDestroyNotify notify) |
303 | { |
304 | GSource *source; |
305 | MainLoopProxy *proxy; |
306 | |
307 | g_return_if_fail (job != NULL); |
308 | g_return_if_fail (func != NULL); |
309 | |
310 | proxy = g_new0 (MainLoopProxy, 1); |
311 | proxy->func = func; |
312 | proxy->data = user_data; |
313 | proxy->notify = notify; |
314 | g_mutex_init (mutex: &proxy->ack_lock); |
315 | g_cond_init (cond: &proxy->ack_condition); |
316 | |
317 | source = g_idle_source_new (); |
318 | g_source_set_priority (source, G_PRIORITY_DEFAULT); |
319 | g_source_set_callback (source, func: mainloop_proxy_func, data: proxy, |
320 | notify: (GDestroyNotify)mainloop_proxy_free); |
321 | g_source_set_name (source, name: "[gio] mainloop_proxy_func" ); |
322 | |
323 | g_source_attach (source, context: job->context); |
324 | g_source_unref (source); |
325 | } |
326 | |