It has taken a lot of debugging and stepping through in order to isolate the potential issue, but we've observed no recurrence of the errors since applying this patch locally and are therefore submitting a PR.
We are in a TimescaleDb / Postgresql 15 environment and are using RepoDb for its binary protocol support for bulk operations.
Findings
Race conditions
When creating db commands during both reads and inserts, the ClassProperty.GetPropertyHandler<TPropertyHandler>() is executed. The returned property handler is used to modify db commands such that their type matches what the database engine expects, and the value of the parameters is converted using the property handler. Quite elegant and also explains why at first we could find no use of Npgsql's TypeMapper system in the Postgresql-specific projects!
However, the code as-is can cause a null return for properties that do have a property handler.
Consider 2 threads:
One enters GetPropertyHandler<TPropertyHandler>(), where propertyHandlerWasSet is false, so it proceeds to set propertyHandlerWasSet = true before it has obtained a reference to PropertyHandlerCache.Get<TPropertyHandler>(GetDeclaringType(), PropertyInfo);. A second thread entering GetPropertyHandler<TPropertyHandler>() at this point can then observe propertyHandlerWasSet to be true, causing the method to return null. When this method returns null, the db parameter is not modified, and leads to errors such as reported in the mentioned issue because the unconverted type is passed through to the database provider.
Hashcode collisions
Furthermore in other areas we observed a chance for hashcode collisions when constructing cache keys, due to summing other hashcodes. Instead, this code has been converted to use HashCode.Combine.
We have been seeing intermittent instances of this issue happening https://github.com/mikependon/RepoDB/issues/1133
It has taken a lot of debugging and stepping through in order to isolate the potential issue, but we've observed no recurrence of the errors since applying this patch locally and are therefore submitting a PR.
We are in a TimescaleDb / Postgresql 15 environment and are using RepoDb for its binary protocol support for bulk operations.
Findings
Race conditions
When creating db commands during both reads and inserts, the
ClassProperty.GetPropertyHandler<TPropertyHandler>()
is executed. The returned property handler is used to modify db commands such that their type matches what the database engine expects, and the value of the parameters is converted using the property handler. Quite elegant and also explains why at first we could find no use of Npgsql's TypeMapper system in the Postgresql-specific projects!However, the code as-is can cause a null return for properties that do have a property handler.
Consider 2 threads:
One enters
GetPropertyHandler<TPropertyHandler>()
, wherepropertyHandlerWasSet
is false, so it proceeds to setpropertyHandlerWasSet = true
before it has obtained a reference toPropertyHandlerCache.Get<TPropertyHandler>(GetDeclaringType(), PropertyInfo);
. A second thread enteringGetPropertyHandler<TPropertyHandler>()
at this point can then observepropertyHandlerWasSet
to betrue
, causing the method to return null. When this method returns null, the db parameter is not modified, and leads to errors such as reported in the mentioned issue because the unconverted type is passed through to the database provider.Hashcode collisions
Furthermore in other areas we observed a chance for hashcode collisions when constructing cache keys, due to summing other hashcodes. Instead, this code has been converted to use
HashCode.Combine
.