Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Proposal: Re-entrantable AsyncLock #31008

Closed
jasonkuo41 opened this issue Sep 28, 2019 · 2 comments
Closed

API Proposal: Re-entrantable AsyncLock #31008

jasonkuo41 opened this issue Sep 28, 2019 · 2 comments
Labels
area-System.Threading untriaged New issue has not been triaged by the area owner

Comments

@jasonkuo41
Copy link

Inspired by #28194 and it's related resources.

Usage

Instead of using semaphore (SempahoreSlim) as a means to use to lock asynchronous content, which may result in deadlock if it is re-acquired without prior releasing on the same Task execution context.

It would be a good idea if we have a low cost, yet that prevents deadlocking upon re-entrant.
Inspired by #28194, I tested out the possibility by combining the usage of AsyncLocal and SempahoreSlim, [Here in this Repo].

Upon writing tests, it is suggested that this is possibly do-able.
Here's more of the detail of how I approached this idea:

AsyncLock utilizes two main classes SemphoreSlim and AsyncLocal; the actual lock is maintained by SemphoreSlim and the re-entrant check is done by using AsyncLocal.

When locking with AsyncLock, it returns an object implementing ILockHandle, which can either contain actual content that does the unlocking when Dispose() is called, or does absolutely nothing. The former one is acquired when the current Task has not acquired AsyncLock, the latter one is returned when AsyncLock detects when it already acquired the lock.

The detection of the current lock acquired by the current task is achieved through AsyncLocal, the underlying value of AsyncLocal is (almost) different when it's in different Task. When it's underlying value is null we know that the current Task hasn't acquired the lock and is put into the semaphore; if it's not null then we know the current Task has already acquired a lock given by this current AsyncLock instance, and would not await the semaphore and returns an empty ILockHandle.

Thoughts and Questions

Few problems araised when there's a limitation of what AsyncLocal can do.
If the lock acquiring process is inside the scope of ExecutionContext.SuppressFlow(), this would effectively break the re-entrant check.
A good solution I came up with would be further integrating with Task, making it maintain a record of acquired AsyncLock without being affected by SuppressFlow (or just make SuppressFlow ignore the tracking of acquired locks)

@svick
Copy link
Contributor

svick commented Oct 5, 2019

I think that reentrant locks are problematic, for reasons explained in this blog post by Stephen Cleary. And while .Net already has reentrant locks (including lock), I don't think more should be added.

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@stephentoub
Copy link
Member

We don't currently have any plans to add such a type to the core libraries. If you come up with a solution that works well for you, please consider releasing it as a nuget package for others who may be interested as well. However, as @svick highlights, this isn't a pattern we intend to promote.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 12, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Threading untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

4 participants