/src/gstreamer/subprojects/gstreamer/gst/gsttask.c
Line | Count | Source |
1 | | /* GStreamer |
2 | | * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> |
3 | | * 2005 Wim Taymans <wim@fluendo.com> |
4 | | * |
5 | | * gsttask.c: Streaming tasks |
6 | | * |
7 | | * This library is free software; you can redistribute it and/or |
8 | | * modify it under the terms of the GNU Library General Public |
9 | | * License as published by the Free Software Foundation; either |
10 | | * version 2 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 | | * Library General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Library General Public |
18 | | * License along with this library; if not, write to the |
19 | | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
20 | | * Boston, MA 02110-1301, USA. |
21 | | */ |
22 | | |
23 | | /** |
24 | | * SECTION:gsttask |
25 | | * @title: GstTask |
26 | | * @short_description: Abstraction of GStreamer streaming threads. |
27 | | * @see_also: #GstElement, #GstPad |
28 | | * |
29 | | * #GstTask is used by #GstElement and #GstPad to provide the data passing |
30 | | * threads in a #GstPipeline. |
31 | | * |
32 | | * A #GstPad will typically start a #GstTask to push or pull data to/from the |
33 | | * peer pads. Most source elements start a #GstTask to push data. In some cases |
34 | | * a demuxer element can start a #GstTask to pull data from a peer element. This |
35 | | * is typically done when the demuxer can perform random access on the upstream |
36 | | * peer element for improved performance. |
37 | | * |
38 | | * Although convenience functions exist on #GstPad to start/pause/stop tasks, it |
39 | | * might sometimes be needed to create a #GstTask manually if it is not related to |
40 | | * a #GstPad. |
41 | | * |
42 | | * Before the #GstTask can be run, it needs a #GRecMutex that can be set with |
43 | | * gst_task_set_lock(). |
44 | | * |
45 | | * The task can be started, paused and stopped with gst_task_start(), gst_task_pause() |
46 | | * and gst_task_stop() respectively or with the gst_task_set_state() function. |
47 | | * |
48 | | * A #GstTask will repeatedly call the #GstTaskFunction with the user data |
49 | | * that was provided when creating the task with gst_task_new(). While calling |
50 | | * the function it will acquire the provided lock. The provided lock is released |
51 | | * when the task pauses or stops. |
52 | | * |
53 | | * Stopping a task with gst_task_stop() will not immediately make sure the task is |
54 | | * not running anymore. Use gst_task_join() to make sure the task is completely |
55 | | * stopped and the thread is stopped. |
56 | | * |
57 | | * After creating a #GstTask, use gst_object_unref() to free its resources. This can |
58 | | * only be done when the task is not running anymore. |
59 | | * |
60 | | * Task functions can send a #GstMessage to send out-of-band data to the |
61 | | * application. The application can receive messages from the #GstBus in its |
62 | | * mainloop. |
63 | | * |
64 | | * For debugging purposes, the task will configure its object name as the thread |
65 | | * name on Linux. Please note that the object name should be configured before the |
66 | | * task is started; changing the object name after the task has been started, has |
67 | | * no effect on the thread name. |
68 | | */ |
69 | | |
70 | | #include "gst_private.h" |
71 | | |
72 | | #include "gstinfo.h" |
73 | | #include "gsttask.h" |
74 | | #include "glib-compat-private.h" |
75 | | |
76 | | #include <stdio.h> |
77 | | |
78 | | #ifdef HAVE_SYS_PRCTL_H |
79 | | #include <sys/prctl.h> |
80 | | #endif |
81 | | |
82 | | #ifdef HAVE_PTHREAD_SETNAME_NP_WITHOUT_TID |
83 | | #include <pthread.h> |
84 | | #endif |
85 | | |
86 | | GST_DEBUG_CATEGORY_STATIC (task_debug); |
87 | | #define GST_CAT_DEFAULT (task_debug) |
88 | | |
89 | 402k | #define SET_TASK_STATE(t,s) (g_atomic_int_set (&GST_TASK_STATE(t), (s))) |
90 | 310k | #define GET_TASK_STATE(t) ((GstTaskState) g_atomic_int_get (&GST_TASK_STATE(t))) |
91 | | |
92 | | static const char * |
93 | | task_state_to_string (GstTaskState state) |
94 | 0 | { |
95 | 0 | switch (state) { |
96 | 0 | case GST_TASK_STARTED: |
97 | 0 | return "started"; |
98 | 0 | case GST_TASK_PAUSED: |
99 | 0 | return "paused"; |
100 | 0 | case GST_TASK_STOPPED: |
101 | 0 | return "stopped"; |
102 | 0 | default: |
103 | 0 | return "(unknown)"; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | struct _GstTaskPrivate |
108 | | { |
109 | | /* callbacks for managing the thread of this task */ |
110 | | GstTaskThreadFunc enter_func; |
111 | | gpointer enter_user_data; |
112 | | GDestroyNotify enter_notify; |
113 | | |
114 | | GstTaskThreadFunc leave_func; |
115 | | gpointer leave_user_data; |
116 | | GDestroyNotify leave_notify; |
117 | | |
118 | | /* configured pool */ |
119 | | GstTaskPool *pool; |
120 | | |
121 | | /* remember the pool and id that is currently running. */ |
122 | | gpointer id; |
123 | | GstTaskPool *pool_id; |
124 | | |
125 | | /* we're currently inside gst_task_join() */ |
126 | | gboolean joining; |
127 | | }; |
128 | | |
129 | | #ifdef _MSC_VER |
130 | | #define WIN32_LEAN_AND_MEAN |
131 | | #include <windows.h> |
132 | | |
133 | | typedef HRESULT (WINAPI * pSetThreadDescription) (HANDLE hThread, |
134 | | PCWSTR lpThreadDescription); |
135 | | static pSetThreadDescription SetThreadDescriptionFunc = NULL; |
136 | | HMODULE kernel32_module = NULL; |
137 | | |
138 | | struct _THREADNAME_INFO |
139 | | { |
140 | | DWORD dwType; // must be 0x1000 |
141 | | LPCSTR szName; // pointer to name (in user addr space) |
142 | | DWORD dwThreadID; // thread ID (-1=caller thread) |
143 | | DWORD dwFlags; // reserved for future use, must be zero |
144 | | }; |
145 | | typedef struct _THREADNAME_INFO THREADNAME_INFO; |
146 | | |
147 | | static void |
148 | | SetThreadName (DWORD dwThreadID, LPCSTR szThreadName) |
149 | | { |
150 | | THREADNAME_INFO info; |
151 | | info.dwType = 0x1000; |
152 | | info.szName = szThreadName; |
153 | | info.dwThreadID = dwThreadID; |
154 | | info.dwFlags = 0; |
155 | | |
156 | | __try { |
157 | | RaiseException (0x406D1388, 0, sizeof (info) / sizeof (DWORD), |
158 | | (const ULONG_PTR *) &info); |
159 | | } |
160 | | __except (EXCEPTION_CONTINUE_EXECUTION) { |
161 | | } |
162 | | } |
163 | | |
164 | | static gboolean |
165 | | gst_task_win32_load_library (void) |
166 | | { |
167 | | /* FIXME: Add support for UWP app */ |
168 | | #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) |
169 | | static gsize _init_once = 0; |
170 | | if (g_once_init_enter (&_init_once)) { |
171 | | kernel32_module = LoadLibraryW (L"kernel32.dll"); |
172 | | if (kernel32_module) { |
173 | | SetThreadDescriptionFunc = |
174 | | (pSetThreadDescription) GetProcAddress (kernel32_module, |
175 | | "SetThreadDescription"); |
176 | | if (!SetThreadDescriptionFunc) |
177 | | FreeLibrary (kernel32_module); |
178 | | } |
179 | | g_once_init_leave (&_init_once, 1); |
180 | | } |
181 | | #endif |
182 | | |
183 | | return !!SetThreadDescriptionFunc; |
184 | | } |
185 | | |
186 | | static gboolean |
187 | | gst_task_win32_set_thread_desc (const gchar * name) |
188 | | { |
189 | | HRESULT hr; |
190 | | wchar_t *namew; |
191 | | |
192 | | if (!gst_task_win32_load_library () || !name) |
193 | | return FALSE; |
194 | | |
195 | | namew = g_utf8_to_utf16 (name, -1, NULL, NULL, NULL); |
196 | | if (!namew) |
197 | | return FALSE; |
198 | | |
199 | | hr = SetThreadDescriptionFunc (GetCurrentThread (), namew); |
200 | | |
201 | | g_free (namew); |
202 | | return SUCCEEDED (hr); |
203 | | } |
204 | | |
205 | | static void |
206 | | gst_task_win32_set_thread_name (const gchar * name) |
207 | | { |
208 | | /* Prefer SetThreadDescription over exception based way if available, |
209 | | * since thread description set by SetThreadDescription will be preserved |
210 | | * in dump file */ |
211 | | if (!gst_task_win32_set_thread_desc (name)) |
212 | | SetThreadName ((DWORD) - 1, name); |
213 | | } |
214 | | #endif |
215 | | |
216 | | static void gst_task_finalize (GObject * object); |
217 | | |
218 | | static void gst_task_func (GstTask * task); |
219 | | |
220 | | static GMutex pool_lock; |
221 | | |
222 | | static GstTaskPool *_global_task_pool = NULL; |
223 | | |
224 | | #define _do_init \ |
225 | | { \ |
226 | | GST_DEBUG_CATEGORY_INIT (task_debug, "task", 0, "Processing tasks"); \ |
227 | | } |
228 | | |
229 | 951k | G_DEFINE_TYPE_WITH_CODE (GstTask, gst_task, GST_TYPE_OBJECT, |
230 | 951k | G_ADD_PRIVATE (GstTask) _do_init); |
231 | 951k | |
232 | 951k | /* Called with pool_lock */ |
233 | 951k | static void |
234 | 951k | ensure_klass_pool (GstTaskClass * klass) |
235 | 951k | { |
236 | 91.6k | if (G_UNLIKELY (_global_task_pool == NULL)) { |
237 | 2 | _global_task_pool = gst_task_pool_new (); |
238 | 2 | gst_task_pool_prepare (_global_task_pool, NULL); |
239 | | |
240 | | /* Classes are never destroyed so this ref will never be dropped */ |
241 | 2 | GST_OBJECT_FLAG_SET (_global_task_pool, GST_OBJECT_FLAG_MAY_BE_LEAKED); |
242 | 2 | } |
243 | 91.6k | klass->pool = _global_task_pool; |
244 | 91.6k | } |
245 | | |
246 | | static void |
247 | | gst_task_class_init (GstTaskClass * klass) |
248 | 9 | { |
249 | 9 | GObjectClass *gobject_class; |
250 | | |
251 | 9 | gobject_class = (GObjectClass *) klass; |
252 | | |
253 | 9 | gobject_class->finalize = gst_task_finalize; |
254 | 9 | } |
255 | | |
256 | | static void |
257 | | gst_task_init (GstTask * task) |
258 | 91.6k | { |
259 | 91.6k | GstTaskClass *klass; |
260 | | |
261 | 91.6k | klass = GST_TASK_GET_CLASS (task); |
262 | | |
263 | 91.6k | task->priv = gst_task_get_instance_private (task); |
264 | 91.6k | task->priv->joining = FALSE; |
265 | 91.6k | task->running = FALSE; |
266 | 91.6k | task->thread = NULL; |
267 | 91.6k | task->lock = NULL; |
268 | 91.6k | g_cond_init (&task->cond); |
269 | 91.6k | SET_TASK_STATE (task, GST_TASK_STOPPED); |
270 | | |
271 | | /* use the default klass pool for this task, users can |
272 | | * override this later */ |
273 | 91.6k | g_mutex_lock (&pool_lock); |
274 | 91.6k | ensure_klass_pool (klass); |
275 | 91.6k | task->priv->pool = gst_object_ref (klass->pool); |
276 | 91.6k | g_mutex_unlock (&pool_lock); |
277 | 91.6k | } |
278 | | |
279 | | static void |
280 | | gst_task_finalize (GObject * object) |
281 | 91.6k | { |
282 | 91.6k | GstTask *task = GST_TASK (object); |
283 | 91.6k | GstTaskPrivate *priv = task->priv; |
284 | | |
285 | 91.6k | GST_DEBUG ("task %p finalize", task); |
286 | | |
287 | 91.6k | if (priv->enter_notify) |
288 | 0 | priv->enter_notify (priv->enter_user_data); |
289 | | |
290 | 91.6k | if (priv->leave_notify) |
291 | 0 | priv->leave_notify (priv->leave_user_data); |
292 | | |
293 | 91.6k | if (task->notify) |
294 | 91.6k | task->notify (task->user_data); |
295 | | |
296 | 91.6k | gst_object_unref (priv->pool); |
297 | | |
298 | | /* task thread cannot be running here since it holds a ref |
299 | | * to the task so that the finalize could not have happened */ |
300 | 91.6k | g_cond_clear (&task->cond); |
301 | | |
302 | 91.6k | G_OBJECT_CLASS (gst_task_parent_class)->finalize (object); |
303 | 91.6k | } |
304 | | |
305 | | /* should be called with the object LOCK */ |
306 | | static void |
307 | | gst_task_configure_name (GstTask * task) |
308 | 91.5k | { |
309 | 91.5k | #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_NAME) |
310 | 91.5k | const gchar *name; |
311 | 91.5k | gchar thread_name[17] = { 0, }; |
312 | | |
313 | 91.5k | GST_OBJECT_LOCK (task); |
314 | 91.5k | name = GST_OBJECT_NAME (task); |
315 | | |
316 | | /* set the thread name to something easily identifiable */ |
317 | 91.5k | if (!snprintf (thread_name, 17, "%s", GST_STR_NULL (name))) { |
318 | 0 | GST_DEBUG_OBJECT (task, "Could not create thread name for '%s'", name); |
319 | 91.5k | } else { |
320 | 91.5k | GST_DEBUG_OBJECT (task, "Setting thread name to '%s'", thread_name); |
321 | 91.5k | if (prctl (PR_SET_NAME, (unsigned long int) thread_name, 0, 0, 0)) |
322 | 91.5k | GST_DEBUG_OBJECT (task, "Failed to set thread name"); |
323 | 91.5k | } |
324 | 91.5k | GST_OBJECT_UNLOCK (task); |
325 | | #elif defined(HAVE_PTHREAD_SETNAME_NP_WITHOUT_TID) |
326 | | const gchar *name; |
327 | | |
328 | | GST_OBJECT_LOCK (task); |
329 | | name = GST_OBJECT_NAME (task); |
330 | | |
331 | | /* set the thread name to something easily identifiable */ |
332 | | GST_DEBUG_OBJECT (task, "Setting thread name to '%s'", name); |
333 | | if (pthread_setname_np (name)) |
334 | | GST_DEBUG_OBJECT (task, "Failed to set thread name"); |
335 | | |
336 | | GST_OBJECT_UNLOCK (task); |
337 | | #elif defined (_MSC_VER) |
338 | | const gchar *name; |
339 | | name = GST_OBJECT_NAME (task); |
340 | | |
341 | | /* set the thread name to something easily identifiable */ |
342 | | GST_DEBUG_OBJECT (task, "Setting thread name to '%s'", name); |
343 | | gst_task_win32_set_thread_name (name); |
344 | | #endif |
345 | 91.5k | } |
346 | | |
347 | | static void |
348 | | gst_task_func (GstTask * task) |
349 | 91.6k | { |
350 | 91.6k | GRecMutex *lock; |
351 | 91.6k | GThread *tself; |
352 | 91.6k | GstTaskPrivate *priv; |
353 | 91.6k | gboolean joining; |
354 | | |
355 | 91.6k | priv = task->priv; |
356 | | |
357 | 91.6k | tself = g_thread_self (); |
358 | | |
359 | 91.6k | GST_DEBUG ("Entering task %p, thread %p", task, tself); |
360 | | |
361 | | /* we have to grab the lock to get the mutex. We also |
362 | | * mark our state running so that nobody can mess with |
363 | | * the mutex. */ |
364 | 91.6k | GST_OBJECT_LOCK (task); |
365 | 91.6k | if (GET_TASK_STATE (task) == GST_TASK_STOPPED) |
366 | 64 | goto exit; |
367 | 91.5k | lock = GST_TASK_GET_LOCK (task); |
368 | 91.5k | if (G_UNLIKELY (lock == NULL)) |
369 | 0 | goto no_lock; |
370 | 91.5k | task->thread = tself; |
371 | 91.5k | GST_OBJECT_UNLOCK (task); |
372 | | |
373 | | /* fire the enter_func callback when we need to */ |
374 | 91.5k | if (priv->enter_func) |
375 | 91.5k | priv->enter_func (task, tself, priv->enter_user_data); |
376 | | |
377 | | /* locking order is TASK_LOCK, LOCK */ |
378 | 91.5k | g_rec_mutex_lock (lock); |
379 | | /* configure the thread name now */ |
380 | 91.5k | gst_task_configure_name (task); |
381 | | |
382 | 541k | while (G_LIKELY (GET_TASK_STATE (task) != GST_TASK_STOPPED)) { |
383 | 485k | GST_OBJECT_LOCK (task); |
384 | 521k | while (G_UNLIKELY (GST_TASK_STATE (task) == GST_TASK_PAUSED)) { |
385 | 35.9k | g_rec_mutex_unlock (lock); |
386 | | |
387 | 35.9k | GST_TASK_SIGNAL (task); |
388 | 35.9k | GST_INFO_OBJECT (task, "Task going to paused"); |
389 | 35.9k | GST_TASK_WAIT (task); |
390 | 35.9k | GST_INFO_OBJECT (task, "Task resume from paused"); |
391 | 35.9k | GST_OBJECT_UNLOCK (task); |
392 | | /* locking order.. */ |
393 | 35.9k | g_rec_mutex_lock (lock); |
394 | 35.9k | GST_OBJECT_LOCK (task); |
395 | 35.9k | } |
396 | | |
397 | 485k | if (G_UNLIKELY (GET_TASK_STATE (task) == GST_TASK_STOPPED)) { |
398 | 35.9k | GST_OBJECT_UNLOCK (task); |
399 | 35.9k | break; |
400 | 449k | } else { |
401 | 449k | GST_OBJECT_UNLOCK (task); |
402 | 449k | } |
403 | | |
404 | 449k | task->func (task->user_data); |
405 | 449k | } |
406 | | |
407 | 91.5k | g_rec_mutex_unlock (lock); |
408 | | |
409 | 91.5k | GST_OBJECT_LOCK (task); |
410 | 91.5k | task->thread = NULL; |
411 | | |
412 | 91.6k | exit: |
413 | 91.6k | if (priv->leave_func) { |
414 | | /* fire the leave_func callback when we need to. We need to do this before |
415 | | * we signal the task and with the task lock released. */ |
416 | 91.6k | GST_OBJECT_UNLOCK (task); |
417 | 91.6k | priv->leave_func (task, tself, priv->leave_user_data); |
418 | 91.6k | GST_OBJECT_LOCK (task); |
419 | 91.6k | } |
420 | | /* now we allow messing with the lock again by setting the running flag to |
421 | | * %FALSE. Together with the SIGNAL this is the sign for the _join() to |
422 | | * complete. */ |
423 | 91.6k | task->running = FALSE; |
424 | 91.6k | GST_TASK_SIGNAL (task); |
425 | | /* Someone called join() while we're exiting, so they are holding |
426 | | * a reference and we can drop ours already. This ensures that finalize() is |
427 | | * called when the joiner's reference is dropped. */ |
428 | 91.6k | joining = task->priv->joining; |
429 | 91.6k | if (joining) |
430 | 69.7k | gst_object_unref (task); |
431 | 91.6k | GST_DEBUG ("Exit task %p, thread %p", task, g_thread_self ()); |
432 | 91.6k | GST_OBJECT_UNLOCK (task); |
433 | | |
434 | 91.6k | if (!joining) |
435 | 21.8k | gst_object_unref (task); |
436 | | |
437 | 91.6k | return; |
438 | | |
439 | 0 | no_lock: |
440 | 0 | { |
441 | 0 | g_warning ("starting task without a lock"); |
442 | 0 | goto exit; |
443 | 91.5k | } |
444 | 91.5k | } |
445 | | |
446 | | /** |
447 | | * gst_task_cleanup_all: |
448 | | * |
449 | | * Wait for all tasks to be stopped. This is mainly used internally |
450 | | * to ensure proper cleanup of internal data structures in test suites. |
451 | | * |
452 | | * MT safe. |
453 | | */ |
454 | | void |
455 | | gst_task_cleanup_all (void) |
456 | 0 | { |
457 | 0 | GstTaskClass *klass; |
458 | |
|
459 | 0 | if ((klass = g_type_class_peek (GST_TYPE_TASK))) { |
460 | 0 | if (klass->pool) { |
461 | 0 | g_mutex_lock (&pool_lock); |
462 | 0 | gst_task_pool_cleanup (klass->pool); |
463 | 0 | gst_object_unref (klass->pool); |
464 | 0 | klass->pool = NULL; |
465 | 0 | _global_task_pool = NULL; |
466 | 0 | g_mutex_unlock (&pool_lock); |
467 | 0 | } |
468 | 0 | } |
469 | | |
470 | | /* Clean up a GThreadPool associated with gst_*_async_call() if any */ |
471 | 0 | _priv_gst_thread_pool_cleanup (); |
472 | 0 | } |
473 | | |
474 | | /** |
475 | | * gst_task_new: |
476 | | * @func: The #GstTaskFunction to use |
477 | | * @user_data: User data to pass to @func |
478 | | * @notify: the function to call when @user_data is no longer needed. |
479 | | * |
480 | | * Create a new Task that will repeatedly call the provided @func |
481 | | * with @user_data as a parameter. Typically the task will run in |
482 | | * a new thread. |
483 | | * |
484 | | * The function cannot be changed after the task has been created. You |
485 | | * must create a new #GstTask to change the function. |
486 | | * |
487 | | * This function will not yet create and start a thread. Use gst_task_start() or |
488 | | * gst_task_pause() to create and start the GThread. |
489 | | * |
490 | | * Before the task can be used, a #GRecMutex must be configured using the |
491 | | * gst_task_set_lock() function. This lock will always be acquired while |
492 | | * @func is called. |
493 | | * |
494 | | * Returns: (transfer full): A new #GstTask. |
495 | | * |
496 | | * MT safe. |
497 | | */ |
498 | | GstTask * |
499 | | gst_task_new (GstTaskFunction func, gpointer user_data, GDestroyNotify notify) |
500 | 91.6k | { |
501 | 91.6k | GstTask *task; |
502 | | |
503 | 91.6k | g_return_val_if_fail (func != NULL, NULL); |
504 | | |
505 | 91.6k | task = g_object_new (GST_TYPE_TASK, NULL); |
506 | 91.6k | task->func = func; |
507 | 91.6k | task->user_data = user_data; |
508 | 91.6k | task->notify = notify; |
509 | | |
510 | 91.6k | GST_DEBUG ("Created task %p", task); |
511 | | |
512 | | /* clear floating flag */ |
513 | 91.6k | gst_object_ref_sink (task); |
514 | | |
515 | 91.6k | return task; |
516 | 91.6k | } |
517 | | |
518 | | /** |
519 | | * gst_task_set_lock: |
520 | | * @task: The #GstTask to use |
521 | | * @mutex: The #GRecMutex to use |
522 | | * |
523 | | * Set the mutex used by the task. The mutex will be acquired before |
524 | | * calling the #GstTaskFunction. |
525 | | * |
526 | | * This function has to be called before calling gst_task_pause() or |
527 | | * gst_task_start(). |
528 | | * |
529 | | * MT safe. |
530 | | */ |
531 | | void |
532 | | gst_task_set_lock (GstTask * task, GRecMutex * mutex) |
533 | 91.6k | { |
534 | 91.6k | g_return_if_fail (GST_IS_TASK (task)); |
535 | | |
536 | 91.6k | GST_OBJECT_LOCK (task); |
537 | 91.6k | if (G_UNLIKELY (task->running)) |
538 | 0 | goto is_running; |
539 | 91.6k | GST_INFO ("setting stream lock %p on task %p", mutex, task); |
540 | 91.6k | GST_TASK_GET_LOCK (task) = mutex; |
541 | 91.6k | GST_OBJECT_UNLOCK (task); |
542 | | |
543 | 91.6k | return; |
544 | | |
545 | | /* ERRORS */ |
546 | 0 | is_running: |
547 | 0 | { |
548 | 0 | GST_OBJECT_UNLOCK (task); |
549 | 0 | g_warning ("cannot call set_lock on a running task"); |
550 | 0 | } |
551 | 0 | } |
552 | | |
553 | | /** |
554 | | * gst_task_get_pool: |
555 | | * @task: a #GstTask |
556 | | * |
557 | | * Get the #GstTaskPool that this task will use for its streaming |
558 | | * threads. |
559 | | * |
560 | | * MT safe. |
561 | | * |
562 | | * Returns: (transfer full): the #GstTaskPool used by @task. gst_object_unref() |
563 | | * after usage. |
564 | | */ |
565 | | GstTaskPool * |
566 | | gst_task_get_pool (GstTask * task) |
567 | 0 | { |
568 | 0 | GstTaskPool *result; |
569 | 0 | GstTaskPrivate *priv; |
570 | |
|
571 | 0 | g_return_val_if_fail (GST_IS_TASK (task), NULL); |
572 | | |
573 | 0 | priv = task->priv; |
574 | |
|
575 | 0 | GST_OBJECT_LOCK (task); |
576 | 0 | result = gst_object_ref (priv->pool); |
577 | 0 | GST_OBJECT_UNLOCK (task); |
578 | |
|
579 | 0 | return result; |
580 | 0 | } |
581 | | |
582 | | /** |
583 | | * gst_task_set_pool: |
584 | | * @task: a #GstTask |
585 | | * @pool: (transfer none): a #GstTaskPool |
586 | | * |
587 | | * Set @pool as the new GstTaskPool for @task. Any new streaming threads that |
588 | | * will be created by @task will now use @pool. |
589 | | * |
590 | | * MT safe. |
591 | | */ |
592 | | void |
593 | | gst_task_set_pool (GstTask * task, GstTaskPool * pool) |
594 | 0 | { |
595 | 0 | GstTaskPool *old; |
596 | 0 | GstTaskPrivate *priv; |
597 | |
|
598 | 0 | g_return_if_fail (GST_IS_TASK (task)); |
599 | 0 | g_return_if_fail (GST_IS_TASK_POOL (pool)); |
600 | | |
601 | 0 | priv = task->priv; |
602 | |
|
603 | 0 | GST_OBJECT_LOCK (task); |
604 | 0 | if (priv->pool != pool) { |
605 | 0 | old = priv->pool; |
606 | 0 | priv->pool = gst_object_ref (pool); |
607 | 0 | } else |
608 | 0 | old = NULL; |
609 | 0 | GST_OBJECT_UNLOCK (task); |
610 | |
|
611 | 0 | if (old) |
612 | 0 | gst_object_unref (old); |
613 | 0 | } |
614 | | |
615 | | /** |
616 | | * gst_task_set_enter_callback: |
617 | | * @task: The #GstTask to use |
618 | | * @enter_func: (in): a #GstTaskThreadFunc |
619 | | * @user_data: user data passed to @enter_func |
620 | | * @notify: called when @user_data is no longer referenced |
621 | | * |
622 | | * Call @enter_func when the task function of @task is entered. @user_data will |
623 | | * be passed to @enter_func and @notify will be called when @user_data is no |
624 | | * longer referenced. |
625 | | */ |
626 | | void |
627 | | gst_task_set_enter_callback (GstTask * task, GstTaskThreadFunc enter_func, |
628 | | gpointer user_data, GDestroyNotify notify) |
629 | 91.6k | { |
630 | 91.6k | GDestroyNotify old_notify; |
631 | | |
632 | 91.6k | g_return_if_fail (task != NULL); |
633 | 91.6k | g_return_if_fail (GST_IS_TASK (task)); |
634 | | |
635 | 91.6k | GST_OBJECT_LOCK (task); |
636 | 91.6k | if ((old_notify = task->priv->enter_notify)) { |
637 | 0 | gpointer old_data = task->priv->enter_user_data; |
638 | |
|
639 | 0 | task->priv->enter_user_data = NULL; |
640 | 0 | task->priv->enter_notify = NULL; |
641 | 0 | GST_OBJECT_UNLOCK (task); |
642 | |
|
643 | 0 | old_notify (old_data); |
644 | |
|
645 | 0 | GST_OBJECT_LOCK (task); |
646 | 0 | } |
647 | 91.6k | task->priv->enter_func = enter_func; |
648 | 91.6k | task->priv->enter_user_data = user_data; |
649 | 91.6k | task->priv->enter_notify = notify; |
650 | 91.6k | GST_OBJECT_UNLOCK (task); |
651 | 91.6k | } |
652 | | |
653 | | /** |
654 | | * gst_task_set_leave_callback: |
655 | | * @task: The #GstTask to use |
656 | | * @leave_func: (in): a #GstTaskThreadFunc |
657 | | * @user_data: user data passed to @leave_func |
658 | | * @notify: called when @user_data is no longer referenced |
659 | | * |
660 | | * Call @leave_func when the task function of @task is left. @user_data will |
661 | | * be passed to @leave_func and @notify will be called when @user_data is no |
662 | | * longer referenced. |
663 | | */ |
664 | | void |
665 | | gst_task_set_leave_callback (GstTask * task, GstTaskThreadFunc leave_func, |
666 | | gpointer user_data, GDestroyNotify notify) |
667 | 91.6k | { |
668 | 91.6k | GDestroyNotify old_notify; |
669 | | |
670 | 91.6k | g_return_if_fail (task != NULL); |
671 | 91.6k | g_return_if_fail (GST_IS_TASK (task)); |
672 | | |
673 | 91.6k | GST_OBJECT_LOCK (task); |
674 | 91.6k | if ((old_notify = task->priv->leave_notify)) { |
675 | 0 | gpointer old_data = task->priv->leave_user_data; |
676 | |
|
677 | 0 | task->priv->leave_user_data = NULL; |
678 | 0 | task->priv->leave_notify = NULL; |
679 | 0 | GST_OBJECT_UNLOCK (task); |
680 | |
|
681 | 0 | old_notify (old_data); |
682 | |
|
683 | 0 | GST_OBJECT_LOCK (task); |
684 | 0 | } |
685 | 91.6k | task->priv->leave_func = leave_func; |
686 | 91.6k | task->priv->leave_user_data = user_data; |
687 | 91.6k | task->priv->leave_notify = notify; |
688 | 91.6k | GST_OBJECT_UNLOCK (task); |
689 | 91.6k | } |
690 | | |
691 | | /** |
692 | | * gst_task_get_state: |
693 | | * @task: The #GstTask to query |
694 | | * |
695 | | * Get the current state of the task. |
696 | | * |
697 | | * Returns: The #GstTaskState of the task |
698 | | * |
699 | | * MT safe. |
700 | | */ |
701 | | GstTaskState |
702 | | gst_task_get_state (GstTask * task) |
703 | 0 | { |
704 | 0 | GstTaskState result; |
705 | |
|
706 | 0 | g_return_val_if_fail (GST_IS_TASK (task), GST_TASK_STOPPED); |
707 | | |
708 | 0 | result = GET_TASK_STATE (task); |
709 | |
|
710 | 0 | return result; |
711 | 0 | } |
712 | | |
713 | | /* make sure the task is running and start a thread if it's not. |
714 | | * This function must be called with the task LOCK. */ |
715 | | static gboolean |
716 | | start_task (GstTask * task) |
717 | 91.6k | { |
718 | 91.6k | gboolean res = TRUE; |
719 | 91.6k | GError *error = NULL; |
720 | 91.6k | GstTaskPrivate *priv; |
721 | | |
722 | 91.6k | priv = task->priv; |
723 | | |
724 | | /* new task, We ref before so that it remains alive while |
725 | | * the thread is running. */ |
726 | 91.6k | gst_object_ref (task); |
727 | | /* mark task as running so that a join will wait until we schedule |
728 | | * and exit the task function. */ |
729 | 91.6k | task->running = TRUE; |
730 | 91.6k | task->priv->joining = FALSE; |
731 | | |
732 | | /* push on the thread pool, we remember the original pool because the user |
733 | | * could change it later on and then we join to the wrong pool. */ |
734 | 91.6k | priv->pool_id = gst_object_ref (priv->pool); |
735 | 91.6k | priv->id = |
736 | 91.6k | gst_task_pool_push (priv->pool_id, (GstTaskPoolFunction) gst_task_func, |
737 | 91.6k | task, &error); |
738 | | |
739 | 91.6k | if (error != NULL) { |
740 | 0 | g_warning ("failed to create thread: %s", error->message); |
741 | 0 | g_error_free (error); |
742 | 0 | res = FALSE; |
743 | 0 | } |
744 | 91.6k | return res; |
745 | 91.6k | } |
746 | | |
747 | | static inline gboolean |
748 | | gst_task_set_state_unlocked (GstTask * task, GstTaskState state) |
749 | 219k | { |
750 | 219k | GstTaskState old; |
751 | 219k | gboolean res = TRUE; |
752 | | |
753 | 219k | GST_DEBUG_OBJECT (task, "Changing task %p to state %s", task, |
754 | 219k | task_state_to_string (state)); |
755 | | |
756 | 219k | if (state != GST_TASK_STOPPED) |
757 | 127k | if (G_UNLIKELY (GST_TASK_GET_LOCK (task) == NULL)) |
758 | 0 | goto no_lock; |
759 | | |
760 | | /* if the state changed, do our thing */ |
761 | 219k | old = GET_TASK_STATE (task); |
762 | 219k | if (old != state) { |
763 | 219k | SET_TASK_STATE (task, state); |
764 | 219k | switch (old) { |
765 | 91.6k | case GST_TASK_STOPPED: |
766 | | /* If the task already has a thread scheduled we don't have to do |
767 | | * anything. */ |
768 | 91.6k | if (G_UNLIKELY (!task->running)) |
769 | 91.6k | res = start_task (task); |
770 | 91.6k | break; |
771 | 35.9k | case GST_TASK_PAUSED: |
772 | | /* when we are paused, signal to go to the new state */ |
773 | 35.9k | GST_TASK_SIGNAL (task); |
774 | 35.9k | break; |
775 | 91.6k | case GST_TASK_STARTED: |
776 | | /* if we were started, we'll go to the new state after the next |
777 | | * iteration. */ |
778 | 91.6k | break; |
779 | 219k | } |
780 | 219k | } |
781 | | |
782 | 219k | return res; |
783 | | |
784 | | /* ERRORS */ |
785 | 0 | no_lock: |
786 | 0 | { |
787 | 0 | GST_WARNING_OBJECT (task, "state %s set on task without a lock", |
788 | 0 | task_state_to_string (state)); |
789 | 0 | g_warning ("task without a lock can't be set to state %s", |
790 | 0 | task_state_to_string (state)); |
791 | 0 | return FALSE; |
792 | 219k | } |
793 | 219k | } |
794 | | |
795 | | |
796 | | /** |
797 | | * gst_task_set_state: |
798 | | * @task: a #GstTask |
799 | | * @state: the new task state |
800 | | * |
801 | | * Sets the state of @task to @state. |
802 | | * |
803 | | * The @task must have a lock associated with it using |
804 | | * gst_task_set_lock() when going to GST_TASK_STARTED or GST_TASK_PAUSED or |
805 | | * this function will return %FALSE. |
806 | | * |
807 | | * MT safe. |
808 | | * |
809 | | * Returns: %TRUE if the state could be changed. |
810 | | */ |
811 | | gboolean |
812 | | gst_task_set_state (GstTask * task, GstTaskState state) |
813 | 219k | { |
814 | 219k | gboolean res = TRUE; |
815 | | |
816 | 219k | g_return_val_if_fail (GST_IS_TASK (task), FALSE); |
817 | | |
818 | 219k | GST_OBJECT_LOCK (task); |
819 | 219k | res = gst_task_set_state_unlocked (task, state); |
820 | 219k | GST_OBJECT_UNLOCK (task); |
821 | | |
822 | 219k | return res; |
823 | 219k | } |
824 | | |
825 | | /** |
826 | | * gst_task_start: |
827 | | * @task: The #GstTask to start |
828 | | * |
829 | | * Starts @task. The @task must have a lock associated with it using |
830 | | * gst_task_set_lock() or this function will return %FALSE. |
831 | | * |
832 | | * Returns: %TRUE if the task could be started. |
833 | | * |
834 | | * MT safe. |
835 | | */ |
836 | | gboolean |
837 | | gst_task_start (GstTask * task) |
838 | 0 | { |
839 | 0 | return gst_task_set_state (task, GST_TASK_STARTED); |
840 | 0 | } |
841 | | |
842 | | /** |
843 | | * gst_task_stop: |
844 | | * @task: The #GstTask to stop |
845 | | * |
846 | | * Stops @task. This method merely schedules the task to stop and |
847 | | * will not wait for the task to have completely stopped. Use |
848 | | * gst_task_join() to stop and wait for completion. |
849 | | * |
850 | | * Returns: %TRUE if the task could be stopped. |
851 | | * |
852 | | * MT safe. |
853 | | */ |
854 | | gboolean |
855 | | gst_task_stop (GstTask * task) |
856 | 20 | { |
857 | 20 | return gst_task_set_state (task, GST_TASK_STOPPED); |
858 | 20 | } |
859 | | |
860 | | /** |
861 | | * gst_task_pause: |
862 | | * @task: The #GstTask to pause |
863 | | * |
864 | | * Pauses @task. This method can also be called on a task in the |
865 | | * stopped state, in which case a thread will be started and will remain |
866 | | * in the paused state. This function does not wait for the task to complete |
867 | | * the paused state. |
868 | | * |
869 | | * Returns: %TRUE if the task could be paused. |
870 | | * |
871 | | * MT safe. |
872 | | */ |
873 | | gboolean |
874 | | gst_task_pause (GstTask * task) |
875 | 0 | { |
876 | 0 | return gst_task_set_state (task, GST_TASK_PAUSED); |
877 | 0 | } |
878 | | |
879 | | /** |
880 | | * gst_task_resume: |
881 | | * @task: The #GstTask to resume |
882 | | * |
883 | | * Resume @task in case it was paused. If the task was stopped, it will |
884 | | * remain in that state and this function will return %FALSE. |
885 | | * |
886 | | * Returns: %TRUE if the task could be resumed. |
887 | | * |
888 | | * MT safe. |
889 | | * Since: 1.18 |
890 | | */ |
891 | | gboolean |
892 | | gst_task_resume (GstTask * task) |
893 | 0 | { |
894 | 0 | gboolean res = FALSE; |
895 | 0 | g_return_val_if_fail (GST_IS_TASK (task), FALSE); |
896 | | |
897 | 0 | GST_OBJECT_LOCK (task); |
898 | 0 | if (GET_TASK_STATE (task) != GST_TASK_STOPPED) |
899 | 0 | res = gst_task_set_state_unlocked (task, GST_TASK_STARTED); |
900 | 0 | GST_OBJECT_UNLOCK (task); |
901 | |
|
902 | 0 | return res; |
903 | 0 | } |
904 | | |
905 | | /** |
906 | | * gst_task_join: |
907 | | * @task: The #GstTask to join |
908 | | * |
909 | | * Joins @task. After this call, it is safe to unref the task |
910 | | * and clean up the lock set with gst_task_set_lock(). |
911 | | * |
912 | | * The task will automatically be stopped with this call. |
913 | | * |
914 | | * This function cannot be called from within a task function as this |
915 | | * would cause a deadlock. The function will detect this and print a |
916 | | * g_warning. |
917 | | * |
918 | | * Returns: %TRUE if the task could be joined. |
919 | | * |
920 | | * MT safe. |
921 | | */ |
922 | | gboolean |
923 | | gst_task_join (GstTask * task) |
924 | 91.6k | { |
925 | 91.6k | GThread *tself; |
926 | 91.6k | GstTaskPrivate *priv; |
927 | 91.6k | gpointer id; |
928 | 91.6k | GstTaskPool *pool = NULL; |
929 | | |
930 | 91.6k | g_return_val_if_fail (GST_IS_TASK (task), FALSE); |
931 | | |
932 | 91.6k | priv = task->priv; |
933 | | |
934 | 91.6k | tself = g_thread_self (); |
935 | | |
936 | 91.6k | GST_DEBUG_OBJECT (task, "Joining task %p, thread %p", task, tself); |
937 | | |
938 | | /* we don't use a real thread join here because we are using |
939 | | * thread pools */ |
940 | 91.6k | GST_OBJECT_LOCK (task); |
941 | 91.6k | if (G_UNLIKELY (tself == task->thread)) |
942 | 0 | goto joining_self; |
943 | 91.6k | priv->joining = TRUE; |
944 | 91.6k | SET_TASK_STATE (task, GST_TASK_STOPPED); |
945 | | /* signal the state change for when it was blocked in PAUSED. */ |
946 | 91.6k | GST_TASK_SIGNAL (task); |
947 | | /* we set the running flag when pushing the task on the thread pool. |
948 | | * This means that the task function might not be called when we try |
949 | | * to join it here. */ |
950 | 161k | while (G_LIKELY (task->running)) |
951 | 69.7k | GST_TASK_WAIT (task); |
952 | | /* clean the thread */ |
953 | 91.6k | task->thread = NULL; |
954 | | /* get the id and pool to join */ |
955 | 91.6k | pool = priv->pool_id; |
956 | 91.6k | id = priv->id; |
957 | 91.6k | priv->pool_id = NULL; |
958 | 91.6k | priv->id = NULL; |
959 | 91.6k | priv->joining = FALSE; |
960 | 91.6k | GST_OBJECT_UNLOCK (task); |
961 | | |
962 | 91.6k | if (pool) { |
963 | 91.6k | if (id) |
964 | 0 | gst_task_pool_join (pool, id); |
965 | 91.6k | gst_object_unref (pool); |
966 | 91.6k | } |
967 | | |
968 | 91.6k | GST_DEBUG_OBJECT (task, "Joined task %p", task); |
969 | | |
970 | 91.6k | return TRUE; |
971 | | |
972 | | /* ERRORS */ |
973 | 0 | joining_self: |
974 | 0 | { |
975 | 0 | GST_WARNING_OBJECT (task, "trying to join task from its thread"); |
976 | 0 | GST_OBJECT_UNLOCK (task); |
977 | 0 | g_warning ("\nTrying to join task %p from its thread would deadlock.\n" |
978 | 0 | "You cannot change the state of an element from its streaming\n" |
979 | 0 | "thread. Use g_idle_add() or post a GstMessage on the bus to\n" |
980 | 0 | "schedule the state change from the main thread.\n", task); |
981 | 0 | return FALSE; |
982 | 91.6k | } |
983 | 91.6k | } |