{"id":531,"date":"2013-12-18T13:00:27","date_gmt":"2013-12-18T13:00:27","guid":{"rendered":"http:\/\/writeasync.net\/?p=531"},"modified":"2013-12-02T05:53:22","modified_gmt":"2013-12-02T05:53:22","slug":"async-exclusive-lock-integration-test","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=531","title":{"rendered":"Async exclusive lock: integration test"},"content":{"rendered":"<p>In the <a title=\"Building an async exclusive lock\" href=\"http:\/\/writeasync.net\/?p=501\">previous post<\/a>, I introduced my <code>ExclusiveLock<\/code>. In the typical TDD style, I ended up with a correct single-threaded implementation. However, as soon as I added <a title=\"Add exception guard to integration test\" href=\"https:\/\/github.com\/brian-dot-net\/writeasync\/commit\/a13ae4fe27350ebf30018ef1fdd4b120dbd9cd6d\">parallelism and exception tracking to the basic integration test<\/a>, I began hitting exceptions indicative of state corruption:<\/p>\n<p><code>System.InvalidOperationException: The token is not valid.<br \/>\nat LockSample.ExclusiveLock.Release(Token token) in writeasync\\projects\\LockSample\\source\\LockSample.Core\\ExclusiveLock.cs:line 46<br \/>\nat LockSample.Program.d__c.MoveNext() in writeasync\\projects\\LockSample\\source\\LockSample.App\\Program.cs:line 78<br \/>\n--- End of stack trace from previous location where exception was thrown ---<br \/>\nat System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)<br \/>\nat System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)<br \/>\nat LockSample.Program.d__8.MoveNext() in writeasync\\projects\\LockSample\\source\\LockSample.App\\Program.cs:line 65<\/code><\/p>\n<p><code><br \/>\nSystem.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.<br \/>\nParameter name: index<br \/>\nat System.Collections.Generic.List`1.RemoveAt(Int32 index)<br \/>\nat LockSample.Program.d__12.MoveNext() in writeasync\\projects\\LockSample\\source\\LockSample.App\\Program.cs:line 114<br \/>\n--- End of stack trace from previous location where exception was thrown ---<br \/>\nat System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)<br \/>\nat System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)<br \/>\nat LockSample.Program.d__c.MoveNext() in writeasync\\projects\\LockSample\\source\\LockSample.App\\Program.cs:line 93<br \/>\n--- End of stack trace from previous location where exception was thrown ---<br \/>\nat System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)<br \/>\nat System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)<br \/>\nat LockSample.Program.d__8.MoveNext() in writeasync\\projects\\LockSample\\source\\LockSample.App\\Program.cs:line 65<br \/>\n<\/code><\/p>\n<p>But first, let&#8217;s talk about the integration test itself. The basic idea is that the <code>ExclusiveLock<\/code> should help enforce exclusive access to a shared resource without corruption of itself or the resource it is protecting. The resource I chose for the test is a simple <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/6sh2ey19(v=vs.110).aspx\" title=\"List&lt;T&gt; Class\"><code>List&lt;int&gt;<\/code><\/a>. This type is not safe for multiple writers nor does it allow writes <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/b0yss765(v=vs.110).aspx\" title=\"List&lt;T&gt;.GetEnumerator Method\">during enumeration<\/a>. The main test loop involved selecting one of four random operations on each iteration. The loop was executed in parallel by multiple actors. In the final refactoring it looked like this:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nwhile (!token.IsCancellationRequested)\r\n{\r\n    ExclusiveLock.Token elt = await l.AcquireAsync();\r\n    try\r\n    {\r\n        await Task.Yield();\r\n        switch (random.Next(4))\r\n        {\r\n            case 0:\r\n                await worker.EnumerateAsync();\r\n                break;\r\n            case 1:\r\n                await worker.AppendAsync();\r\n                break;\r\n            case 2:\r\n                await worker.RemoveAsync();\r\n                break;\r\n            case 3:\r\n                await worker.RemoveAllAsync();\r\n                break;\r\n        }\r\n    }\r\n    finally\r\n    {\r\n        l.Release(elt);\r\n    }\r\n}\r\n<\/pre>\n<p>After getting the exceptions above, <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync\/commit\/0ee83d9c815aad9808b1091c2e04f3b661a12cf0\" title=\"Thread safety for ExclusiveLock\">I added proper locking<\/a>. The concurrency fixes were relatively simple this time. I chose the <code>nextOwners<\/code> queue as my sync root and basically locked almost all of the work in <code>AcquireAsync<\/code> and <code>Release<\/code>. (As usual, I left the <code>SetResult<\/code> code path out of the lock scope in the pending waiter case inside <code>Release<\/code>.)<\/p>\n<p>After the fixes, my integration test app ran properly. The app only appended or removed from the end of the list and always added in increasing index order. The validation was thus that the list when enumerated should be a monotonically increasing sequence starting from <code>1<\/code>. No state corruption was detected and the simple verification passed every time it ran.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous post, I introduced my ExclusiveLock. In the typical TDD style, I ended up with a correct single-threaded implementation. However, as soon as I added parallelism and exception tracking to the basic integration test, I began hitting exceptions&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21,61,51],"tags":[],"class_list":["post-531","post","type-post","status-publish","format-standard","hentry","category-async","category-concurrency","category-testing"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/531","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=531"}],"version-history":[{"count":0,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/531\/revisions"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=531"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}