testclock: add support for waiting and releasing multiple GstClockIDs
authorHavard Graff <havard.graff@gmail.com>
Mon, 16 Dec 2013 09:01:37 +0000 (10:01 +0100)
committerTim-Philipp Müller <tim@centricular.com>
Sat, 12 Apr 2014 14:33:31 +0000 (15:33 +0100)
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.

libs/gst/check/Makefile.am
libs/gst/check/gsttestclock.c
libs/gst/check/gsttestclock.h
tests/check/libs/gsttestclock.c

index 155bff8..73c0cdf 100644 (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) \
index 4b3ee8a..685330c 100644 (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:
  *
@@ -824,37 +876,6 @@ gst_test_clock_wait_for_next_pending_id (GstTestClock * 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
  * notification
@@ -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);
+}
index 699e169..2b54b22 100644 (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__ */
index ab0c13d..97a04c2 100644 (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);