{"action":"created","issue":{"url":"https://api.github.com/repos/StephenCleary/AsyncEx/issues/36","repository_url":"https://api.github.com/repos/StephenCleary/AsyncEx","labels_url":"https://api.github.com/repos/StephenCleary/AsyncEx/issues/36/labels{/name}","comments_url":"https://api.github.com/repos/StephenCleary/AsyncEx/issues/36/comments","events_url":"https://api.github.com/repos/StephenCleary/AsyncEx/issues/36/events","html_url":"https://github.com/StephenCleary/AsyncEx/issues/36","id":113585460,"number":36,"title":"StackOverflowException and Thread stealing when combining LockAsync and sync code","user":{"login":"KlausLinzner","id":13362720,"avatar_url":"https://avatars.githubusercontent.com/u/13362720?v=3","gravatar_id":"","url":"https://api.github.com/users/KlausLinzner","html_url":"https://github.com/KlausLinzner","followers_url":"https://api.github.com/users/KlausLinzner/followers","following_url":"https://api.github.com/users/KlausLinzner/following{/other_user}","gists_url":"https://api.github.com/users/KlausLinzner/gists{/gist_id}","starred_url":"https://api.github.com/users/KlausLinzner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KlausLinzner/subscriptions","organizations_url":"https://api.github.com/users/KlausLinzner/orgs","repos_url":"https://api.github.com/users/KlausLinzner/repos","events_url":"https://api.github.com/users/KlausLinzner/events{/privacy}","received_events_url":"https://api.github.com/users/KlausLinzner/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":5,"created_at":"2015-10-27T13:38:14Z","updated_at":"2016-06-21T19:11:22Z","closed_at":null,"body":"When using AsyncLock.LockAsync() in combination with only synchronous running code a StackOverflowException can happen during dispose/unlock. \r\nThe following code snippet runs into the StackOverflow:\r\n```cs\r\npublic static async Task NitoRunIntoStackOverflowAsync()\r\n{\r\n    const int locksToTake = 5000;\r\n    AsyncLock nitoLock = new AsyncLock();\r\n\r\n    Task[] tasks = new Task[locksToTake];\r\n\r\n    using (await nitoLock.LockAsync())\r\n    {\r\n        for (int i = 0; i < locksToTake; i++)\r\n        {\r\n            tasks[i] = Task.Run(async () =>\r\n            {\r\n                using (await nitoLock.LockAsync())\r\n                {\r\n                    //Any synchronous running code...\r\n                    await Task.FromResult(true);\r\n                }\r\n            });\r\n        }\r\n    }//Disposing will trigger the stackoverflow\r\n\r\n    await Task.WhenAll(tasks);\r\n}\r\n```\r\n\r\nWill result in the following stack (taken from windbg - read bottom up):\r\n```\r\nNito.AsyncEx.AsyncLock+Key.System.IDisposable.Dispose()\r\n//class.method awaiting the lock\r\nSystem.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)\r\nSystem.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)\r\nSystem.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run()\r\nSystem.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(System.Action, Boolean, System.Threading.Tasks.Task ByRef)\r\nSystem.Threading.Tasks.Task.FinishContinuations()\r\nSystem.Threading.Tasks.Task`1[[System.__Canon, mscorlib]].TrySetResult(System.__Canon)\r\nSystem.Threading.Tasks.TaskCompletionSource`1[[System.__Canon, mscorlib]].TrySetResult(System.__Canon)\r\nNito.AsyncEx.DefaultAsyncWaitQueue`1+CompleteDisposable[[System.__Canon, mscorlib]].Dispose()\r\nNito.AsyncEx.AsyncLock.ReleaseLock()\r\nNito.AsyncEx.AsyncLock+Key.System.IDisposable.Dispose()\r\n```\r\n\r\nShouldn't the call to `TaskCompletionSource<T>.TrySetResult` in the `CompleteDisposable` be replaced with either an offload like `TrySetResultWithBackgroundContinuations` despite the performance implications or one of the possibilities from this [SO thread](http://stackoverflow.com/questions/22579206/how-can-i-prevent-synchronous-continuations-on-a-task) (as far as I remember you participated in there)"},"comment":{"url":"https://api.github.com/repos/StephenCleary/AsyncEx/issues/comments/227541345","html_url":"https://github.com/StephenCleary/AsyncEx/issues/36#issuecomment-227541345","issue_url":"https://api.github.com/repos/StephenCleary/AsyncEx/issues/36","id":227541345,"user":{"login":"glen-nicol","id":5103200,"avatar_url":"https://avatars.githubusercontent.com/u/5103200?v=3","gravatar_id":"","url":"https://api.github.com/users/glen-nicol","html_url":"https://github.com/glen-nicol","followers_url":"https://api.github.com/users/glen-nicol/followers","following_url":"https://api.github.com/users/glen-nicol/following{/other_user}","gists_url":"https://api.github.com/users/glen-nicol/gists{/gist_id}","starred_url":"https://api.github.com/users/glen-nicol/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glen-nicol/subscriptions","organizations_url":"https://api.github.com/users/glen-nicol/orgs","repos_url":"https://api.github.com/users/glen-nicol/repos","events_url":"https://api.github.com/users/glen-nicol/events{/privacy}","received_events_url":"https://api.github.com/users/glen-nicol/received_events","type":"User","site_admin":false},"created_at":"2016-06-21T19:11:22Z","updated_at":"2016-06-21T19:11:22Z","body":"I am having similar trouble running 3.0.1. I have the following wrapper class around AsyncMonitor\r\n\r\n```csharp\r\npublic class EasyAsyncMonitor\r\n{\r\n\tprivate AsyncMonitor _monitor = new AsyncMonitor();\r\n\r\n\tpublic Task WaitAsync(CancellationToken token = default(CancellationToken))\r\n\t{\r\n\t\treturn EnterMonitorAndDo(() =>\r\n\t\t{\r\n\t\t\treturn _monitor.WaitAsync();\r\n\t\t}, token);\r\n\t}\r\n\r\n\tpublic Task Pulse(CancellationToken token = default(CancellationToken))\r\n\t{\r\n\t\treturn EnterMonitorAndDo(() => \r\n\t\t{\r\n\t\t\t_monitor.Pulse();\r\n\t\t}, token);\r\n\t}\r\n\r\n\tpublic Task PulseAll(CancellationToken token = default(CancellationToken))\r\n\t{\r\n\t\treturn EnterMonitorAndDo(() => \r\n\t\t{\r\n\t\t\t_monitor.PulseAll();\r\n\t\t}, token);\r\n\t}\r\n\r\n\tprivate async Task EnterMonitorAndDo(Func<Task> doThis, CancellationToken token)\r\n\t{\r\n\t\tusing(await _monitor.EnterAsync(token))\r\n\t\t{\r\n\t\t\tawait doThis().DetachContext(); //Overflow occurs here\r\n\t\t}\r\n\t}\r\n\r\n\tprivate async Task EnterMonitorAndDo(Action doThis, CancellationToken token)\r\n\t{\r\n\t\tusing(await _monitor.EnterAsync(token))\r\n\t\t{\r\n\t\t\tdoThis();\r\n\t\t}\r\n\t}\r\n}\r\n```\r\n\r\nAnd DetachContext is defined as \r\n\r\n```csharp\r\npublic static ConfiguredTaskAwaitable DetachContext(this Task t)\r\n{\r\n\treturn t.ConfigureAwait(false);\r\n}\r\n```\r\n\r\nOccasionally I will get a stackoverflow exception thrown from the line indicated above with the comment. The stack trace is unavailable but is visible in Visual studio debugging. It is full of that single line and then [Resuming Async Method] and [External code] entries between them.\r\n\r\nCould this be related or is it incorrect usage of the Monitor?\r\n"}}
{"actor":{"display_login":"glen-nicol"}}