[folks] Add test cases for mutexing for async calls from the same thread.



commit 1492a80a0a05988b32b1d49895002a738c68ebfc
Author: Travis Reitter <travis reitter collabora co uk>
Date:   Wed Jul 6 11:09:26 2011 -0700

    Add test cases for mutexing for async calls from the same thread.
    
    The standard Vala lock() call is a recursive lock, so it only prevents
    concurrent access between different threads. But we rarely run in
    distinct threads, so this test uses a more-complete solution that we can
    depend upon (since the test will fail if its assumptions ever change in
    Vala).
    
    Helps: bgo#652637 - Don't hold locks across async calls

 tests/folks/Makefile.am        |    6 ++
 tests/folks/async-locking.vala |  160 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 166 insertions(+), 0 deletions(-)
---
diff --git a/tests/folks/Makefile.am b/tests/folks/Makefile.am
index 62bd9df..470485e 100644
--- a/tests/folks/Makefile.am
+++ b/tests/folks/Makefile.am
@@ -45,6 +45,7 @@ AM_VALAFLAGS = \
 # in order from least to most complex
 noinst_PROGRAMS = \
 	abstract-field-details \
+	async-locking \
 	utils \
 	backend-loading \
 	aggregation \
@@ -79,6 +80,10 @@ abstract_field_details_SOURCES = \
 	abstract-field-details.vala \
 	$(NULL)
 
+async_locking_SOURCES = \
+	async-locking.vala \
+	$(NULL)
+
 utils_SOURCES = \
 	utils.vala \
 	$(NULL)
@@ -106,6 +111,7 @@ MAINTAINERCLEANFILES = \
         backend_loading_vala.stamp \
         aggregation_vala.stamp \
         abstract_field_details_vala.stamp \
+        async_locking_vala.stamp \
         utils_vala.stamp \
         avatar_cache_vala.stamp \
         object_cache_vala.stamp \
diff --git a/tests/folks/async-locking.vala b/tests/folks/async-locking.vala
new file mode 100644
index 0000000..9957031
--- /dev/null
+++ b/tests/folks/async-locking.vala
@@ -0,0 +1,160 @@
+using Gee;
+using GLib;
+
+public class AsyncLockingTests : Folks.TestCase
+{
+  int _counter;
+  bool _counter_increment_pending;
+  int _calls_pending;
+  int _total_calls;
+  MainLoop _loop;
+
+  public AsyncLockingTests ()
+    {
+      base ("AsyncLocking");
+      this.add_test ("many concurrent funcs", this.test_many_concurrent_funcs);
+      this.add_test ("many concurrent funcs (safe)",
+          this.test_many_concurrent_funcs_safe);
+    }
+
+  public override void set_up ()
+    {
+      this._counter = 0;
+      this._calls_pending = 0;
+      this._counter_increment_pending = false;
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_many_concurrent_funcs ()
+    {
+      _loop = new GLib.MainLoop (null, false);
+      this._total_calls = 100;
+
+      Idle.add (() =>
+        {
+          for (var i = 0; i < this._total_calls; i++)
+            {
+              this._calls_pending++;
+              locked_increment.begin (increment_handler);
+            }
+
+          return false;
+        });
+
+      _loop.run ();
+    }
+
+  private void increment_handler (GLib.Object? source, GLib.AsyncResult result)
+    {
+      locked_increment.end (result);
+
+      /* calls expect the end state when they reach this point */
+      assert (this._counter >= 1);
+
+      this._calls_pending--;
+      if (this._calls_pending == 0)
+        {
+          print ("\n    final counter value: %d " +
+              "(would be 1 if this weren't intentionally broken)\n",
+              this._counter);
+          this._loop.quit ();
+        }
+    }
+
+  private async void locked_increment ()
+    {
+      lock (this._counter)
+        {
+          if (this._counter < 1)
+            {
+              /* In this unsafe version, all the async calls will reach this
+               * point (despite the fact that they're all in the same thread).
+               * Uncomment the print() call below to verify. */
+
+              yield simulate_work ();
+
+              /* XXX: uncomment for debugging
+              print ("    %3d -> %3d\n", this._counter, this._counter + 1);
+              */
+              this._counter++;
+            }
+        }
+    }
+
+  public void test_many_concurrent_funcs_safe ()
+    {
+      _loop = new GLib.MainLoop (null, false);
+      this._total_calls = 100;
+      this._calls_pending = 0;
+      this._counter_increment_pending = false;
+
+      Idle.add (() =>
+        {
+          for (var i = 0; i < this._total_calls; i++)
+            {
+              this._calls_pending++;
+              locked_increment_safe.begin (increment_handler_safe);
+            }
+
+          return false;
+        });
+
+      _loop.run ();
+    }
+
+  private void increment_handler_safe (GLib.Object? source,
+      GLib.AsyncResult result)
+    {
+      locked_increment_safe.end (result);
+
+      /* calls "ignored" while the first is in-flight still expect the end state
+       * when they reach this point */
+      assert (this._counter == 1);
+
+      this._calls_pending--;
+      if (this._calls_pending == 0)
+        {
+          print ("\n    final counter value: %d\n", this._counter);
+          assert (this._counter == 1);
+
+          this._loop.quit ();
+        }
+    }
+
+  private async void locked_increment_safe ()
+    {
+      lock (this._counter)
+        {
+          if (this._counter < 1 && !this._counter_increment_pending)
+            {
+              this._counter_increment_pending = true;
+              yield simulate_work ();
+
+              /* XXX: uncomment for debugging
+              print ("    %3d -> %3d\n", this._counter, this._counter + 1);
+              */
+              this._counter++;
+            }
+        }
+    }
+
+  private async void simulate_work ()
+    {
+      Thread.usleep (50 * 1000);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AsyncLockingTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]