testclock: add support for waiting and releasing multiple GstClockIDs

In order to be deterministic, multiple waiting GstClockIDs needs to be
released at the same time, or else one can get into the situation that
the one being released first can add itself back again before the next
one waiting is released.

Test added for new API and old tests rewritten to comply.
This commit is contained in:
Havard Graff 2013-12-16 10:01:37 +01:00 committed by Tim-Philipp Müller
parent cb554772e5
commit 3892cc4c2d
4 changed files with 290 additions and 73 deletions

View file

@ -102,9 +102,14 @@ LIBGSTCHECK_EXPORTED_FUNCS = \
gst_test_clock_has_id \
gst_test_clock_peek_next_pending_id \
gst_test_clock_wait_for_next_pending_id \
gst_test_clock_wait_for_pending_id_count \
gst_test_clock_wait_for_multiple_pending_ids \
gst_test_clock_process_next_clock_id \
gst_test_clock_get_next_entry_time
gst_test_clock_get_next_entry_time \
gst_test_clock_wait_for_multiple_pending_ids \
gst_test_clock_process_id_list \
gst_test_clock_id_list_get_latest_time \
gst_test_clock_id_list_free
LIBGSTCHECK_EXPORTED_SYMBOLS = \
$(LIBGSTCHECK_EXPORTED_VARS) \

View file

@ -2,6 +2,8 @@
*
* Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
* Copyright (C) 2012 Sebastian Rasmussen <sebastian.rasmussen@axis.com>
* Copyright (C) 2012 Havard Graff <havard@pexip.com>
* Copyright (C) 2013 Haakon Sporsheim <haakon@pexip.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@ -62,7 +64,7 @@
*
* #GstTestClock keeps track of these clock notifications. By calling
* gst_test_clock_wait_for_next_pending_id() or
* gst_test_clock_wait_for_pending_id_count() a unit tests may wait for the
* gst_test_clock_wait_for_multiple_pending_ids() a unit tests may wait for the
* next one or several clock notifications to be requested. Additionally unit
* tests may release blocked waits in a controlled fashion by calling
* gst_test_clock_process_next_clock_id(). This way a unit test can control the
@ -76,7 +78,7 @@
*
* N.B.: When a unit test waits for a certain amount of clock notifications to
* be requested in gst_test_clock_wait_for_next_pending_id() or
* gst_test_clock_wait_for_pending_id_count() then these functions may block
* gst_test_clock_wait_for_multiple_pending_ids() then these functions may block
* for a long time. If they block forever then the expected clock notifications
* were never requested from #GstTestClock, and so the assumptions in the code
* of the unit test are wrong. The unit test case runner in gstcheck is
@ -388,6 +390,7 @@ gst_test_clock_set_property (GObject * object, guint property_id,
static GstClockTime
gst_test_clock_get_resolution (GstClock * clock)
{
(void) clock;
return 1;
}
@ -592,6 +595,55 @@ gst_clock_entry_context_compare_func (gconstpointer a, gconstpointer b)
return gst_clock_id_compare_func (ctx_a->clock_entry, ctx_b->clock_entry);
}
static void
process_entry_context_unlocked (GstTestClock * test_clock,
GstClockEntryContext * ctx)
{
GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
GstClockEntry *entry = ctx->clock_entry;
if (ctx->time_diff >= 0)
GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_OK;
else
GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_EARLY;
if (entry->func != NULL) {
GST_OBJECT_UNLOCK (test_clock);
entry->func (GST_CLOCK (test_clock), priv->internal_time, entry,
entry->user_data);
GST_OBJECT_LOCK (test_clock);
}
gst_test_clock_remove_entry (test_clock, entry);
if (GST_CLOCK_ENTRY_TYPE (entry) == GST_CLOCK_ENTRY_PERIODIC) {
GST_CLOCK_ENTRY_TIME (entry) += GST_CLOCK_ENTRY_INTERVAL (entry);
if (entry->func != NULL)
gst_test_clock_add_entry (test_clock, entry, NULL);
}
}
static GstTestClockIDList *
gst_test_clock_get_pending_id_list_unlocked (GstTestClock * test_clock)
{
GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
GstTestClockIDList *result = g_new0 (GstTestClockIDList, 1);
if (priv->entry_contexts != NULL) {
GList *cur;
for (cur = priv->entry_contexts; cur != NULL; cur = cur->next) {
GstClockEntryContext *ctx = cur->data;
result->cur =
g_list_append (result->cur, gst_clock_id_ref (ctx->clock_entry));
}
}
result->length = g_list_length (result->cur);
return result;
}
/**
* gst_test_clock_new:
*
@ -823,37 +875,6 @@ gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock,
GST_OBJECT_UNLOCK (test_clock);
}
/**
* gst_test_clock_wait_for_pending_id_count:
* @test_clock: #GstTestClock for which to await having enough pending clock
* @count: the number of pending clock notifications to wait for
*
* Blocks until at least @count clock notifications have been requested from
* @test_clock. There is no timeout for this wait, see the main description of
* #GstTestClock.
*
* MT safe.
*
* Since: 1.2
*/
void
gst_test_clock_wait_for_pending_id_count (GstTestClock * test_clock,
guint count)
{
GstTestClockPrivate *priv;
g_return_if_fail (GST_IS_TEST_CLOCK (test_clock));
priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
GST_OBJECT_LOCK (test_clock);
while (gst_test_clock_peek_id_count_unlocked (test_clock) < count)
g_cond_wait (&priv->entry_added_cond, GST_OBJECT_GET_LOCK (test_clock));
GST_OBJECT_UNLOCK (test_clock);
}
/**
* gst_test_clock_process_next_clock_id:
* @test_clock: a #GstTestClock for which to retrieve the next pending clock
@ -888,30 +909,8 @@ gst_test_clock_process_next_clock_id (GstTestClock * test_clock)
result = gst_clock_id_ref (ctx->clock_entry);
}
if (result != NULL) {
GstClockEntry *entry = ctx->clock_entry;
if (ctx->time_diff >= 0)
GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_OK;
else
GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_EARLY;
if (entry->func != NULL) {
GST_OBJECT_UNLOCK (test_clock);
entry->func (GST_CLOCK (test_clock), priv->internal_time, entry,
entry->user_data);
GST_OBJECT_LOCK (test_clock);
}
gst_test_clock_remove_entry (test_clock, entry);
if (GST_CLOCK_ENTRY_TYPE (entry) == GST_CLOCK_ENTRY_PERIODIC) {
GST_CLOCK_ENTRY_TIME (entry) += GST_CLOCK_ENTRY_INTERVAL (entry);
if (entry->func != NULL)
gst_test_clock_add_entry (test_clock, entry, NULL);
}
}
if (result != NULL)
process_entry_context_unlocked (test_clock, ctx);
GST_OBJECT_UNLOCK (test_clock);
@ -957,3 +956,125 @@ gst_test_clock_get_next_entry_time (GstTestClock * test_clock)
return result;
}
/**
* gst_test_clock_wait_for_multiple_pending_ids:
* @test_clock: #GstTestClock for which to await having enough pending clock
* @count: the number of pending clock notifications to wait for
* @pending_list: A #GstTestClockIDList with pending #GstClockIDs
*
* Blocks until at least @count clock notifications have been requested from
* @test_clock. There is no timeout for this wait, see the main description of
* #GstTestClock.
*
* MT safe.
*
* Since: 1.2
*/
void
gst_test_clock_wait_for_multiple_pending_ids (GstTestClock * test_clock,
guint count, GstTestClockIDList ** pending_list)
{
GstTestClockPrivate *priv;
g_return_if_fail (GST_IS_TEST_CLOCK (test_clock));
priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
GST_OBJECT_LOCK (test_clock);
while (g_list_length (priv->entry_contexts) < count)
g_cond_wait (&priv->entry_added_cond, GST_OBJECT_GET_LOCK (test_clock));
if (pending_list)
*pending_list = gst_test_clock_get_pending_id_list_unlocked (test_clock);
GST_OBJECT_UNLOCK (test_clock);
}
/**
* gst_test_clock_process_id_list:
* @test_clock: #GstTestClock for which to process the pending IDs
* @pending_list: A #GstTestClockIDList with pending #GstClockIDs
*
* Processes and releases the pending IDs in #GstTestClockIDList
*
* MT safe.
*
* Since: 1.2
*/
guint
gst_test_clock_process_id_list (GstTestClock * test_clock,
GstTestClockIDList * pending_list)
{
GstTestClockPrivate *priv;
GList *cur;
guint result = 0;
g_return_val_if_fail (GST_IS_TEST_CLOCK (test_clock), 0);
priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
GST_OBJECT_LOCK (test_clock);
for (cur = pending_list->cur; cur != NULL; cur = cur->next) {
GstClockID pending_id = cur->data;
GstClockEntryContext *ctx =
gst_test_clock_lookup_entry_context (test_clock, pending_id);
if (ctx) {
process_entry_context_unlocked (test_clock, ctx);
result++;
}
}
GST_OBJECT_UNLOCK (test_clock);
return result;
}
/**
* gst_test_clock_id_list_get_latest_time:
* @pending_list: A #GstTestClockIDList with pending #GstClockIDs
*
* Finds the latest time inside the #GstTestClockIDList
*
* MT safe.
*
* Since: 1.2
*/
GstClockTime
gst_test_clock_id_list_get_latest_time (GstTestClockIDList * pending_list)
{
GList *cur;
GstClockTime result = 0;
for (cur = pending_list->cur; cur != NULL; cur = cur->next) {
GstClockID *pending_id = cur->data;
GstClockTime time = gst_clock_id_get_time (pending_id);
if (time > result)
result = time;
}
return result;
}
/**
* gst_test_clock_id_list_free:
* @pending_list: A #GstTestClockIDList with pending #GstClockIDs
*
* Free the supplied #GstTestClockIDList
*
* MT safe.
*
* Since: 1.2
*/
void
gst_test_clock_id_list_free (GstTestClockIDList * pending_list)
{
GList *cur;
for (cur = pending_list->cur; cur != NULL; cur = cur->next) {
GstClockID *pending_id = cur->data;
gst_clock_id_unref (pending_id);
}
g_list_free (pending_list->cur);
g_free (pending_list);
}

View file

@ -2,6 +2,8 @@
*
* Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
* Copyright (C) 2012 Sebastian Rasmussen <sebastian.rasmussen@axis.com>
* Copyright (C) 2012 Havard Graff <havard@pexip.com>
* Copyright (C) 2013 Haakon Sporsheim <haakon@pexip.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@ -43,6 +45,8 @@ typedef struct _GstTestClock GstTestClock;
typedef struct _GstTestClockClass GstTestClockClass;
typedef struct _GstTestClockPrivate GstTestClockPrivate;
typedef struct _GstTestClockIDList GstTestClockIDList;
/**
* GstTestClock:
*
@ -72,17 +76,32 @@ struct _GstTestClockClass
GstClockClass parent_class;
};
/**
* GstTestClockIDList:
* @cur: A #GList with all pending #GstClockID
* @length: A #guint with the length of the list
*
* A #GstTestClockIDList structure, which is returned when waiting for multiple IDs
*
* Since: 1.2
*/
struct _GstTestClockIDList
{
GList * cur;
guint length;
};
GType gst_test_clock_get_type (void);
GstClock * gst_test_clock_new (void);
GstClock * gst_test_clock_new_with_start_time (GstClockTime start_time);
void gst_test_clock_set_time (GstTestClock * test_clock,
GstClockTime new_time);
void gst_test_clock_set_time (GstTestClock * test_clock,
GstClockTime new_time);
void gst_test_clock_advance_time (GstTestClock * test_clock,
GstClockTimeDiff delta);
void gst_test_clock_advance_time (GstTestClock * test_clock,
GstClockTimeDiff delta);
guint gst_test_clock_peek_id_count (GstTestClock * test_clock);
@ -91,16 +110,24 @@ gboolean gst_test_clock_has_id (GstTestClock * test_clock, GstClockID id);
gboolean gst_test_clock_peek_next_pending_id (GstTestClock * test_clock,
GstClockID * pending_id);
void gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock,
GstClockID * pending_id);
void gst_test_clock_wait_for_pending_id_count (GstTestClock * test_clock,
guint count);
void gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock,
GstClockID * pending_id);
GstClockID gst_test_clock_process_next_clock_id (GstTestClock * test_clock);
GstClockTime gst_test_clock_get_next_entry_time (GstTestClock * test_clock);
void gst_test_clock_wait_for_multiple_pending_ids (GstTestClock * test_clock,
guint count,
GstTestClockIDList ** pending_list);
guint gst_test_clock_process_id_list (GstTestClock * test_clock,
GstTestClockIDList * pending_list);
GstClockTime gst_test_clock_id_list_get_latest_time (GstTestClockIDList * list);
void gst_test_clock_id_list_free (GstTestClockIDList * list);
G_END_DECLS
#endif /* __GST_TEST_CLOCK_H__ */

View file

@ -630,13 +630,13 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout)
context_b.clock_id = gst_clock_id_ref (clock_id_b);
context_b.jitter = 0;
gst_test_clock_wait_for_pending_id_count (test_clock, 0);
gst_test_clock_wait_for_multiple_pending_ids (test_clock, 0, NULL);
worker_thread_b =
g_thread_new ("worker_thread_b",
test_wait_pending_single_shot_id_sync_worker, &context_b);
gst_test_clock_wait_for_pending_id_count (test_clock, 1);
gst_test_clock_wait_for_multiple_pending_ids (test_clock, 1, NULL);
gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
assert_pending_id (pending_id, clock_id_b, GST_CLOCK_ENTRY_SINGLE,
6 * GST_SECOND);
@ -646,7 +646,7 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout)
g_thread_new ("worker_thread_a",
test_wait_pending_single_shot_id_sync_worker, &context_a);
gst_test_clock_wait_for_pending_id_count (test_clock, 2);
gst_test_clock_wait_for_multiple_pending_ids (test_clock, 2, NULL);
gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
assert_pending_id (pending_id, clock_id_a, GST_CLOCK_ENTRY_SINGLE,
5 * GST_SECOND);
@ -660,7 +660,7 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout)
GST_CLOCK_OK);
gst_clock_id_unref (processed_id);
gst_test_clock_wait_for_pending_id_count (test_clock, 1);
gst_test_clock_wait_for_multiple_pending_ids (test_clock, 1, NULL);
gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
assert_pending_id (pending_id, clock_id_b, GST_CLOCK_ENTRY_SINGLE,
6 * GST_SECOND);
@ -674,7 +674,7 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout)
GST_CLOCK_OK);
gst_clock_id_unref (processed_id);
gst_test_clock_wait_for_pending_id_count (test_clock, 0);
gst_test_clock_wait_for_multiple_pending_ids (test_clock, 0, NULL);
g_thread_join (worker_thread_a);
g_thread_join (worker_thread_b);
@ -690,7 +690,70 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout)
gst_object_unref (clock);
}
GST_END_TEST;
GST_START_TEST (test_processing_multiple_ids)
{
GstClock *clock;
GstTestClock *test_clock;
GstClockID clock_id_a;
GstClockID clock_id_b;
SyncClockWaitContext context_a;
SyncClockWaitContext context_b;
GThread *worker_thread_a;
GThread *worker_thread_b;
GstTestClockIDList * pending_list;
clock = gst_test_clock_new_with_start_time (GST_SECOND);
test_clock = GST_TEST_CLOCK (clock);
/* register a wait for 5 seconds */
clock_id_a = gst_clock_new_single_shot_id (clock, 5 * GST_SECOND);
context_a.clock_id = gst_clock_id_ref (clock_id_a);
context_a.jitter = 0;
worker_thread_a =
g_thread_new ("worker_thread_a",
test_wait_pending_single_shot_id_sync_worker, &context_a);
/* register another wait for 6 seconds */
clock_id_b = gst_clock_new_single_shot_id (clock, 6 * GST_SECOND);
context_b.clock_id = gst_clock_id_ref (clock_id_b);
context_b.jitter = 0;
worker_thread_b =
g_thread_new ("worker_thread_b",
test_wait_pending_single_shot_id_sync_worker, &context_b);
/* wait for two waits */
gst_test_clock_wait_for_multiple_pending_ids (test_clock, 2, &pending_list);
/* assert they are correct */
assert_pending_id (pending_list->cur->data, clock_id_a, GST_CLOCK_ENTRY_SINGLE,
5 * GST_SECOND);
assert_pending_id (pending_list->cur->next->data, clock_id_b, GST_CLOCK_ENTRY_SINGLE,
6 * GST_SECOND);
/* verify we are waiting for 6 seconds as the latest time */
fail_unless_equals_int64 (6 * GST_SECOND,
gst_test_clock_id_list_get_latest_time (pending_list));
/* process both ID's at the same time */
gst_test_clock_process_id_list (test_clock, pending_list);
gst_test_clock_id_list_free (pending_list);
g_thread_join (worker_thread_a);
g_thread_join (worker_thread_b);
fail_unless_equals_int64 (-4 * GST_SECOND, context_a.jitter);
fail_unless_equals_int64 (-5 * GST_SECOND, context_b.jitter);
gst_clock_id_unref (context_a.clock_id);
gst_clock_id_unref (context_b.clock_id);
gst_clock_id_unref (clock_id_a);
gst_clock_id_unref (clock_id_b);
gst_object_unref (clock);
}
GST_END_TEST;
GST_START_TEST (test_single_shot_async_past)
@ -954,6 +1017,7 @@ gst_test_clock_suite (void)
tcase_add_test (tc_chain, test_wait_pending_single_shot_id);
tcase_add_test (tc_chain, test_wait_pending_periodic_id);
tcase_add_test (tc_chain, test_single_shot_sync_simultaneous_no_timeout);
tcase_add_test (tc_chain, test_processing_multiple_ids);
tcase_add_test (tc_chain, test_single_shot_sync_past);
tcase_add_test (tc_chain, test_single_shot_sync_present);
tcase_add_test (tc_chain, test_single_shot_sync_future);