-
Notifications
You must be signed in to change notification settings - Fork 352
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Fix race condition in ClientEdmModel.GetOrCreateEdmType #2533
Fix race condition in ClientEdmModel.GetOrCreateEdmType #2533
Conversation
LGTM -- Do you want to create an issue for 8.0 to revisit this locking logic? |
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.
🚢
Done: #2536 |
This PR has Quantification details
Why proper sizing of changes matters
Optimal pull request sizes drive a better predictable PR flow as they strike a
What can I do to optimize my changes
How to interpret the change counts in git diff output
Was this comment helpful? 👍 :ok_hand: :thumbsdown: (Email) |
Issues
*This pull request fixes #2532
This removes a race condition from
ClientEdmModel.GetOrCreateEdmTypeInternal
method that allows one thread to store an instance of an EDM type inClientEdmModel.typeNameToClientTypeAnnotationCache
dictionary and another thread to store a different instance representing the same EDM type inClientEdmModel.clrToEdmTypeCache
. Having instances representing the same EDM type causes the ODataWriter to throw a validation exception because it thinks they are two different types that don't match.For more details about the cause of the issue, read this article
Description
The fix puts the
GetOrCreateClientTypeAnnotation
call (which updates thetypeNameToClientTypeAnnotationCache
) and the code that was below it in the same lock "scope" the code that updates theclrToEdmTypeCache
. This ensures that the thread that inserts the edm type in the first cache also inserts it in the second cache before releasing the lock (which would provide a chance to other threads to "overtake it" and get to the second cache first).I found this to be the simplest fix and the least disruptive. I think we can find better and more efficient solutions, but I think the code here is sufficiently complex that it may be better to rethink the design another time. Since we're adding more code to the same lock's critical section, we might increase lock contention. But at the same, there's one less lock to fight for. More importantly, we've eliminated the bug.
The
GetOrCreateClientTypeAnnotation
method still acquires the lock totypeNameToClientTypeAnnotationCache
, I considered removing this lock because this method is only called byGetOrCreateEdmTypeInternal
and the cache is not updated/locked by another method. However, I thought doing so might require more effort to reason about the correctness of the code, and easier to make a mistake in the future. Also, since only one thread at a time will call this method, there will be no contention for this lock. And apparently, it's relatively cheap to acquire a free lock.Before implementing the fix, I created a failing unit test that reproduces the race condition. It spawns two threads that each calls
GetOrCreateEdmType()
on the same CLR type, then verifies whether the edm type is the same as the one returned from the annotation and the model. Then it runs this 5000 times. I tried different number of iterations:The test may have some false positives, but if it fails we'll know there's a bug.
After implementing the fix, the test passes consistently.
Checklist (Uncheck if it is not completed)
Additional work necessary
If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.