-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Fix thread-safety of JsonDocument.GetString/TextEquals #76450
Conversation
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis Issue DetailsFixes #76440 I'd still like to understand whether this cache is really valuable; if it's not, we should just delete it rather than try to improve its thread-safety. But if keeping it is valuable, this PR should do it.
|
I'm having similar thoughts. The cache appears to want to optimize repeated |
@bartonjs, was there a specific situation for which this cache was introduced? |
I wonder if whatever fix we end up making here would meet the bar for servicing .NET 6 and .NET 7. |
To the best of my recollection, it is that the method was going to be a property at some point in the design life. Or I was being uncharacteristically nice about perf in the debugger (if that applies). |
And, FWIW, the general rule of the platform is that anything that doesn't say it's thread-safe isn't. Though that does get hard for people to identify when it's two instances from their perspective are tied to the same backing instance. So the cache here was probably never considered as relevant for multi-threading |
We did document it... the docs were just updated to remove that: |
It's exposed via some properties, e.g. JsonProperty.Name calls JsonElement.GetPropertyName which calls GetNameOfPropertyValue which calls GetString. |
So that's the scenario. Since properties are supposed to be (modulo a one time memo-ization) roughly field-fast, it shouldn't be re-evaluating a UTF-8 to UTF-16LE transformation every time. e.g. for (int i = prop.Name.Length - 1; i > 0; i--)
{
if (HasKnownValue(prop.Name.AsSpan(0, i)))
{
Console.WriteLine($"A property prefix matched at length {i}: {prop.Name.AsSpan(0, i)}");
return true;
}
}
return false /* presumably */; Since it's a property that looks OK (though, admittedly, contrived), but it's actually an N^2 effort loop with N^2 memory overhead if the caching goes away. |
With or without this PR that falls apart if the document being read from is being read concurrently: it might look like the property access is non-allocating, and you might even measure it and find that it's not, but when multiple threads start reading from it concurrently, they just thrash the cache of a single item per document. And without any concurrency, you'd have the same issue with something like: foreach (string name in names)
{
if (property1.Name == name ||
property2.Name == name)
{
return true;
}
} where those two property.Name accesses are just going to keep overwriting the same cached value. Do we know of any real uses where the cache is helping meaningfully? If yes, and if we want to continue claiming that JsonDocument is thread-safe, we should merge this PR. |
I'd be inclined to just delete it, personally. |
The other thing I had thought of (long ago) was to make something like a ConcurrentDictionary based on the index position, so the back and forth thrash wouldn't happen (but it made the allocation profile harder to describe). So, yeah; I guess deleting the one item cache is probably the most straightforward |
Should I close this one and file a separate PR? |
Sure. |
Fixes #76440
I'd still like to understand whether this cache is really valuable; if it's not, we should just delete it rather than try to improve its thread-safety.
But if keeping it is valuable, this PR should do it.