-
Notifications
You must be signed in to change notification settings - Fork 1.4k
ReaderWriterLock in PropertyDictionary #4374
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
ReaderWriterLock in PropertyDictionary #4374
Conversation
7122e2c to
9194da0
Compare
Fix dotnet#4368 by using a ReaderWriterLockSlim instead of a full lock. This will allow the case where a.Equals(b) and b.Equals(a) run concurrently, since they both only want a reader lock on their _properties: to enumerate their own properties, and on the other to index in.
The previous implementation attempted to lock inside ToDictionary(), and then again inside GetEnumerator() from the foreach. Instead of moving to a recursive lock, which is discouraged in the ReaderWriterLockSlim docs, access _properties.Values directly in ToDictionary().
9194da0 to
ddf872e
Compare
|
On a related note, is there any possibility that one of the Clearly this won't happen in the particular case where |
| using System.Diagnostics; | ||
| using Microsoft.Build.Shared; | ||
| using Microsoft.Build.Evaluation; | ||
| using System.Threading; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: ordering.
| get | ||
| { | ||
| lock (_properties) | ||
| _lock.EnterReadLock(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth creating a struct that implements IDisposable so you can use a using statement instead of adding a try/finally with new indent.
| /// </summary> | ||
| ~PropertyDictionary() | ||
| { | ||
| _lock?.Dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed? If the finalizer thread is used to run dispose on the lock, why not just cut out the middleman and let the finalizer thread finalize the lock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me whether it does or not. I was aping the ReaderWriterLockSlim doc example. But I see several current examples in CoreFX that don't dispose it, like CodePagesEncodingProvider and Privilege. So I guess it's ok to skip it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephentoub any reason we're inconsistent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason we're inconsistent?
In Privilege, it's static.
And CodePagesEncodingProvider exposes a static singleton that contains the instance, so it's essentially static there as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the slim version even require disposing?
ReaderWriterLockSlim wraps several event wait handles. If the RWLS isn't disposed, they'll be left for finalization.
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
…rable it sometimes uses
| /// <summary> | ||
| /// The object used to synchronize access for copying. | ||
| /// </summary> | ||
| private readonly object _syncRoot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can eliminate this field now and use the RW lock? It doesn't matter what the object is so long as it's readonly and private. That will claw back the size increase in this object form adding the RW lock field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disregard, I didn't view the full diff. You still use that syncroot in another path.
| /// <summary> | ||
| /// Lock object to guard access to backing collection. | ||
| /// </summary> | ||
| private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to reuse the DisposableReaderWriterLockSlim, and maybe make it its own file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In reply to: 302275736 [](ancestors = 302275736)
…ry-readerwriterlockslim
…y-readerwriterlockslim
This reverts commit 7b931cf.
This is necessary for the GetCopyOnReadEnumerable case; it gets a read lock in GetCopyOnReadEnumerable.GetEnumerator and then calls PropertyDictionary.GetEnumerator within that.
| IEnumerator<KeyValuePair<string, T>> IEnumerable<KeyValuePair<string, T>>.GetEnumerator() | ||
| { | ||
| lock (_properties) | ||
| _lock.EnterReadLock(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method was and continues to be highly suspect. Maybe it's fine because you control all the callers as well, but the lock is going to be held while this method yields. In other words:
PropertyDictionary pd = ...;
var e = pd.GetEnumerator();
e.MoveNext();
... // lock is held here!
Fix #4368 by using a ReaderWriterLockSlim instead of a full lock. This
will allow the case where a.Equals(b) and b.Equals(a) run concurrently,
since they both only want a reader lock on their _properties: to
enumerate their own properties, and on the other to index in.
@tmeschter suggested this approach offline, and it seems obviously correct (in hindsight!).