Commit 1492a80a authored by Travis Reitter's avatar Travis Reitter Committed by Jeremy Whiting
Browse files

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
parent 67f8e41b
......@@ -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 \
......
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;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment