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