| /* GStreamer |
| * Copyright (C) 2017 Matthew Waters <matthew@centricular.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include "gst_private.h" |
| |
| #include "gstpromise.h" |
| |
| #define GST_CAT_DEFAULT gst_promise_debug |
| GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
| |
| /** |
| * SECTION:gstpromise |
| * @title: GstPromise |
| * @short_description: a miniobject for future/promise-like functionality |
| * @see_also: |
| * |
| * The #GstPromise object implements the container for values that may |
| * be available later. i.e. a Future or a Promise in |
| * <ulink url="https://en.wikipedia.org/wiki/Futures_and_promises">https://en.wikipedia.org/wiki/Futures_and_promises</ulink> |
| * |
| * A #GstPromise can be created with gst_promise_new(), replied to |
| * with gst_promise_reply(), interrupted with gst_promise_interrupt() and |
| * expired with gst_promise_expire(). A callback can also be installed at |
| * #GstPromise creation for result changes with gst_promise_new_with_change_func(). |
| * The change callback can be used to chain #GstPromises's together as in the |
| * following example. |
| * |[<!-- language="C" --> |
| * const GstStructure *reply; |
| * GstPromise *p; |
| * if (gst_promise_wait (promise) != GST_PROMISE_RESULT_REPLIED) |
| * return; // interrupted or expired value |
| * reply = gst_promise_get_reply (promise); |
| * if (error in reply) |
| * return; // propagate error |
| * p = gst_promise_new_with_change_func (another_promise_change_func, user_data, notify); |
| * pass p to promise-using API |
| * ]| |
| * |
| * Each #GstPromise starts out with a #GstPromiseResult of |
| * %GST_PROMISE_RESULT_PENDING and only ever transitions out of that result |
| * into one of the other #GstPromiseResult. |
| * |
| * In order to support multi-threaded code, gst_promise_reply(), |
| * gst_promise_interrupt() and gst_promise_expire() may all be from |
| * different threads with some restrictions, the final result of the promise |
| * is whichever call is made first. There are two restrictions on ordering: |
| * |
| * 1. That gst_promise_reply() and gst_promise_interrupt() cannot be called |
| * after gst_promise_expire() |
| * 2. That gst_promise_reply() and gst_promise_interrupt() |
| * cannot be called twice. |
| */ |
| |
| static const int immutable_structure_refcount = 2; |
| |
| #define GST_PROMISE_REPLY(p) (((GstPromiseImpl *)(p))->reply) |
| #define GST_PROMISE_RESULT(p) (((GstPromiseImpl *)(p))->result) |
| #define GST_PROMISE_LOCK(p) (&(((GstPromiseImpl *)(p))->lock)) |
| #define GST_PROMISE_COND(p) (&(((GstPromiseImpl *)(p))->cond)) |
| #define GST_PROMISE_CHANGE_FUNC(p) (((GstPromiseImpl *)(p))->change_func) |
| #define GST_PROMISE_CHANGE_DATA(p) (((GstPromiseImpl *)(p))->user_data) |
| #define GST_PROMISE_CHANGE_NOTIFY(p) (((GstPromiseImpl *)(p))->notify) |
| |
| typedef struct |
| { |
| GstPromise promise; |
| |
| GstPromiseResult result; |
| GstStructure *reply; |
| |
| GMutex lock; |
| GCond cond; |
| GstPromiseChangeFunc change_func; |
| gpointer user_data; |
| GDestroyNotify notify; |
| } GstPromiseImpl; |
| |
| /** |
| * gst_promise_wait: |
| * @promise: a #GstPromise |
| * |
| * Wait for @promise to move out of the %GST_PROMISE_RESULT_PENDING state. |
| * If @promise is not in %GST_PROMISE_RESULT_PENDING then it will return |
| * immediately with the current result. |
| * |
| * Returns: the result of the promise |
| * |
| * Since: 1.14 |
| */ |
| GstPromiseResult |
| gst_promise_wait (GstPromise * promise) |
| { |
| GstPromiseResult ret; |
| |
| g_return_val_if_fail (promise != NULL, GST_PROMISE_RESULT_EXPIRED); |
| |
| g_mutex_lock (GST_PROMISE_LOCK (promise)); |
| ret = GST_PROMISE_RESULT (promise); |
| |
| while (ret == GST_PROMISE_RESULT_PENDING) { |
| GST_LOG ("%p waiting", promise); |
| g_cond_wait (GST_PROMISE_COND (promise), GST_PROMISE_LOCK (promise)); |
| ret = GST_PROMISE_RESULT (promise); |
| } |
| GST_LOG ("%p waited", promise); |
| |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_promise_reply: |
| * @promise: (allow-none): a #GstPromise |
| * @s: (transfer full): a #GstStructure with the the reply contents |
| * |
| * Set a reply on @promise. This will wake up any waiters with |
| * %GST_PROMISE_RESULT_REPLIED. |
| * |
| * Since: 1.14 |
| */ |
| void |
| gst_promise_reply (GstPromise * promise, GstStructure * s) |
| { |
| GstPromiseChangeFunc change_func = NULL; |
| gpointer change_data = NULL; |
| |
| /* Caller requested that no reply is necessary */ |
| if (promise == NULL) |
| return; |
| |
| g_mutex_lock (GST_PROMISE_LOCK (promise)); |
| if (GST_PROMISE_RESULT (promise) != GST_PROMISE_RESULT_PENDING && |
| GST_PROMISE_RESULT (promise) != GST_PROMISE_RESULT_INTERRUPTED) { |
| GstPromiseResult result = GST_PROMISE_RESULT (promise); |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| g_return_if_fail (result == GST_PROMISE_RESULT_PENDING || |
| result == GST_PROMISE_RESULT_INTERRUPTED); |
| } |
| |
| /* XXX: is this necessary and valid? */ |
| if (GST_PROMISE_REPLY (promise) && GST_PROMISE_REPLY (promise) != s) |
| gst_structure_free (GST_PROMISE_REPLY (promise)); |
| |
| /* Only reply iff we are currently in pending */ |
| if (GST_PROMISE_RESULT (promise) == GST_PROMISE_RESULT_PENDING) { |
| if (s |
| && !gst_structure_set_parent_refcount (s, |
| (int *) &immutable_structure_refcount)) { |
| g_critical ("Input structure has a parent already!"); |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| return; |
| } |
| |
| GST_PROMISE_RESULT (promise) = GST_PROMISE_RESULT_REPLIED; |
| GST_LOG ("%p replied", promise); |
| |
| GST_PROMISE_REPLY (promise) = s; |
| |
| change_func = GST_PROMISE_CHANGE_FUNC (promise); |
| change_data = GST_PROMISE_CHANGE_DATA (promise); |
| } else { |
| /* eat the value */ |
| if (s) |
| gst_structure_free (s); |
| } |
| |
| g_cond_broadcast (GST_PROMISE_COND (promise)); |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| |
| if (change_func) |
| change_func (promise, change_data); |
| } |
| |
| /** |
| * gst_promise_get_reply: |
| * @promise: a #GstPromise |
| * |
| * Retrieve the reply set on @promise. @promise must be in |
| * %GST_PROMISE_RESULT_REPLIED and is owned by @promise |
| * |
| * Returns: (transfer none): The reply set on @promise |
| * |
| * Since: 1.14 |
| */ |
| const GstStructure * |
| gst_promise_get_reply (GstPromise * promise) |
| { |
| g_return_val_if_fail (promise != NULL, NULL); |
| |
| g_mutex_lock (GST_PROMISE_LOCK (promise)); |
| if (GST_PROMISE_RESULT (promise) != GST_PROMISE_RESULT_REPLIED) { |
| GstPromiseResult result = GST_PROMISE_RESULT (promise); |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| g_return_val_if_fail (result == GST_PROMISE_RESULT_REPLIED, NULL); |
| } |
| |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| |
| return GST_PROMISE_REPLY (promise); |
| } |
| |
| /** |
| * gst_promise_interrupt: |
| * @promise: a #GstPromise |
| * |
| * Interrupt waiting for a @promise. This will wake up any waiters with |
| * %GST_PROMISE_RESULT_INTERRUPTED |
| * |
| * Since: 1.14 |
| */ |
| void |
| gst_promise_interrupt (GstPromise * promise) |
| { |
| GstPromiseChangeFunc change_func = NULL; |
| gpointer change_data = NULL; |
| |
| g_return_if_fail (promise != NULL); |
| |
| g_mutex_lock (GST_PROMISE_LOCK (promise)); |
| if (GST_PROMISE_RESULT (promise) != GST_PROMISE_RESULT_PENDING && |
| GST_PROMISE_RESULT (promise) != GST_PROMISE_RESULT_REPLIED) { |
| GstPromiseResult result = GST_PROMISE_RESULT (promise); |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| g_return_if_fail (result == GST_PROMISE_RESULT_PENDING || |
| result == GST_PROMISE_RESULT_REPLIED); |
| } |
| /* only interrupt if we are currently in pending */ |
| if (GST_PROMISE_RESULT (promise) == GST_PROMISE_RESULT_PENDING) { |
| GST_PROMISE_RESULT (promise) = GST_PROMISE_RESULT_INTERRUPTED; |
| g_cond_broadcast (GST_PROMISE_COND (promise)); |
| GST_LOG ("%p interrupted", promise); |
| |
| change_func = GST_PROMISE_CHANGE_FUNC (promise); |
| change_data = GST_PROMISE_CHANGE_DATA (promise); |
| } |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| |
| if (change_func) |
| change_func (promise, change_data); |
| } |
| |
| /** |
| * gst_promise_expire: |
| * @promise: a #GstPromise |
| * |
| * Expire a @promise. This will wake up any waiters with |
| * %GST_PROMISE_RESULT_EXPIRED |
| * |
| * Since: 1.14 |
| */ |
| void |
| gst_promise_expire (GstPromise * promise) |
| { |
| GstPromiseChangeFunc change_func = NULL; |
| gpointer change_data = NULL; |
| |
| g_return_if_fail (promise != NULL); |
| |
| g_mutex_lock (GST_PROMISE_LOCK (promise)); |
| if (GST_PROMISE_RESULT (promise) == GST_PROMISE_RESULT_PENDING) { |
| GST_PROMISE_RESULT (promise) = GST_PROMISE_RESULT_EXPIRED; |
| g_cond_broadcast (GST_PROMISE_COND (promise)); |
| GST_LOG ("%p expired", promise); |
| |
| change_func = GST_PROMISE_CHANGE_FUNC (promise); |
| change_data = GST_PROMISE_CHANGE_DATA (promise); |
| GST_PROMISE_CHANGE_FUNC (promise) = NULL; |
| GST_PROMISE_CHANGE_DATA (promise) = NULL; |
| } |
| g_mutex_unlock (GST_PROMISE_LOCK (promise)); |
| |
| if (change_func) |
| change_func (promise, change_data); |
| } |
| |
| static void |
| gst_promise_free (GstMiniObject * object) |
| { |
| GstPromise *promise = (GstPromise *) object; |
| |
| /* the promise *must* be dealt with in some way before destruction */ |
| g_warn_if_fail (GST_PROMISE_RESULT (promise) != GST_PROMISE_RESULT_PENDING); |
| |
| if (GST_PROMISE_CHANGE_NOTIFY (promise)) |
| GST_PROMISE_CHANGE_NOTIFY (promise) (GST_PROMISE_CHANGE_DATA (promise)); |
| |
| if (GST_PROMISE_REPLY (promise)) { |
| gst_structure_set_parent_refcount (GST_PROMISE_REPLY (promise), NULL); |
| gst_structure_free (GST_PROMISE_REPLY (promise)); |
| } |
| g_mutex_clear (GST_PROMISE_LOCK (promise)); |
| g_cond_clear (GST_PROMISE_COND (promise)); |
| GST_LOG ("%p finalized", promise); |
| |
| g_free (promise); |
| } |
| |
| static void |
| gst_promise_init (GstPromise * promise) |
| { |
| static volatile gsize _init = 0; |
| |
| if (g_once_init_enter (&_init)) { |
| GST_DEBUG_CATEGORY_INIT (gst_promise_debug, "gstpromise", 0, "gstpromise"); |
| g_once_init_leave (&_init, 1); |
| } |
| |
| gst_mini_object_init (GST_MINI_OBJECT (promise), 0, GST_TYPE_PROMISE, NULL, |
| NULL, gst_promise_free); |
| |
| GST_PROMISE_REPLY (promise) = NULL; |
| GST_PROMISE_RESULT (promise) = GST_PROMISE_RESULT_PENDING; |
| g_mutex_init (GST_PROMISE_LOCK (promise)); |
| g_cond_init (GST_PROMISE_COND (promise)); |
| } |
| |
| /** |
| * gst_promise_new: |
| * |
| * Returns: a new #GstPromise |
| * |
| * Since: 1.14 |
| */ |
| GstPromise * |
| gst_promise_new (void) |
| { |
| GstPromise *promise = GST_PROMISE (g_new0 (GstPromiseImpl, 1)); |
| |
| gst_promise_init (promise); |
| GST_LOG ("new promise %p", promise); |
| |
| return promise; |
| } |
| |
| /** |
| * gst_promise_new_with_change_func: |
| * @func: (scope notified): a #GstPromiseChangeFunc to call |
| * @user_data: (closure): argument to call @func with |
| * @notify: notification function that @user_data is no longer needed |
| * |
| * @func will be called exactly once when transitioning out of |
| * %GST_PROMISE_RESULT_PENDING into any of the other #GstPromiseResult |
| * states. |
| * |
| * Returns: a new #GstPromise |
| * |
| * Since: 1.14 |
| */ |
| GstPromise * |
| gst_promise_new_with_change_func (GstPromiseChangeFunc func, gpointer user_data, |
| GDestroyNotify notify) |
| { |
| GstPromise *promise = gst_promise_new (); |
| |
| GST_PROMISE_CHANGE_FUNC (promise) = func; |
| GST_PROMISE_CHANGE_DATA (promise) = user_data; |
| GST_PROMISE_CHANGE_NOTIFY (promise) = notify; |
| |
| return promise; |
| } |
| |
| GST_DEFINE_MINI_OBJECT_TYPE (GstPromise, gst_promise); |