-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Concurrency bug in DefaultContractResolver #1163
Comments
HashTable is not safe for read/writer at the same time. We should update the documentation... |
@davkean I am pretty sure |
It does attempt to go to great lengths to attempt to this - unfortunately, it wasn't successful and still had lots of threading issues (when I'm next connected to the VPN, I'll look up the bugs we had around this if you'd like). In the end, this is why Dictionary wasn't made safe for a writer and reader at the same time - lock free data structures are hard. |
@davkean I think it is really important that you make this publically that there are issues with |
But in case |
Would be great to know the bugs. Our usage in Serilog is pretty forgiving (I don't think we care if we see stale reads, and we don't enumerate or have hard requirements for a correct Seems a bit late to change the docs here though; perhaps adding the known-issues list would be more useful? |
It is but you are incorrect that it's not thread safe. The dictionary assigned to
|
Crap! You are right. I missed the newing of the dictionaries. You are already reference swapping. |
This doesn't invalidate the analysis of the reported problem though. I suspect there to still be a concurrency problem somewhere in JSON.NET (at least in v7). I advised my client to upgrade to v9 and to add an extra guard clause to make it very clear when |
@davkean, do you have any public announcement about HashTable and the possible problems with it? It would really help the whole community and libraries like Serilog that depend on hashtable, quite extensively. |
Just this week a client of mine stumbled on a concurrency bug in the application which I traced back to JSON.NET. The application uses JSON.NET v7.0.1 but after reviewing the code, my conclusion is that the bug still exists in v9.0.1.
Below I'll describe why I conclude the problem is in JSON.NET.
Analysis of the encountered bug in the application
The application runs the following code:
In short, the
JsonExecute
method is a WCF service call that accepts a query message, serialized asjson
where the message type is supplied asqueryTypeName
. Internally, the method converts thequeryTypeName
to the actualType
using a dictionary that is initialized during application start-up and after that is only read from.Based on this query
Type
, aIQueryHandler<TQuery, TResult>
implementation (a query handler) is requested from the use DI container, after which this handler is invoked using C# dynamic.Things to note in this code:
knownQueryTypes
dictionary is never changed after creation.JsonConvert.DeserializeObject
and the call toContainer.GetInstance
.After this web service has ran for several years, this week the following exception ocurred:
With the following stack trace
In the log we saw that the
Ftom the call was for the following request:
During that day all following requests for this particular query failed with the same exception. After the service was restarted the problem disapeared. We haven't seen it.
After close inspection of the code I came to the conclusion that the call to
JsonConvert.DeserializeObject
returned an instance of the incorrect type, because:knownQueryTypes[queryTypeName]
can't return the incorrect type, since the dictionary is never changed after creation.queryType
is correct, the correct query handler must be requested from the container.Handle(GetAnalyseAanvraagViewByIdQuery)
, but that it doesn't match.This means that the only possible reason for the C# binder to report that the method signature doesn't match (and overload resolution fails) is because the actual type of the
query
instance (created byJsonConvert.DeserializeObject
) is different from the requestedqueryType
. Also note that this application runs unchanged in production for quite some time and used a lot.Analysis of the JSON.NET code base
After coming to the conclusion that the problem must be in JSON.NET, I started looking at the code base of JSON.NET 7.0.1 and I found the following code that might explain why things go wrong (note the following code is extract from v7.0.1 using Reflector):
After analyzing this code we can quickly spot a concurrency bug:
contractCache.TryGetValue
is used outside thelock
.Dictionary<TKey, TValue>
can not safely be used with one concurrent writer. MSDN clearly states:In other words, there is a concurrency bug in the
JsonContract
cache, and that might explain problem described above. If theDefaultContractResolver
resolves aJsonContract
for the wrong type, deserialization can fail silently, since that type can usually be created successfully, but it will be incorrectly initialized. In this case all query objects have default constructors. Deserialization would likely fail if the query class was immutable with a non-default constructor.A possible fix for this problem is to replace the
Dictionary<ResolverContractKey, JsonContract>
with an old fashionedHashTable
, since:Do note that the latest code contains the same bug. This means that even v9.0.1 is affected.
The text was updated successfully, but these errors were encountered: