Potential threading issues with .Query<T>
See original GitHub issueI’m running into a scenario where I’ve got calls into a class that handles some data, and noticed that occasionally I get Object reference not set to an instance of an object, or that the command has been disposed.
Upon googling, I found an old issue here (that I can’t seem to find now) that states that DataConnection is not thread safe, that a new DataConnection should be created from each thread.
My concern is I’m not always sure the origin in the calls to that data class. An example could be someone spawning a new task, then calling that pre-existing instance, or trying to query data from a timer callback.
I’m just trying to find any decent way of guard other users against running into this sort of thing.
Being that I don’t know enough about linq2db’s internals to know why it’s not thread safe, is it something that could feasibly be added?
I feel like at this point my only option to handle this (to ensure dataconnection per thread) would be to blindly create new dataconnections each and every call/attempt to do anything in my data class.
Exception message:
Stack trace:
System.ObjectDisposedException
Cannot access a disposed object.
Object name: 'OracleCommand'.
at Oracle.ManagedDataAccess.Client.OracleCommand.ValidateStatePriorToExecution()
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior)
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)
at LinqToDB.Data.DataConnection.ExecuteReader(CommandBehavior commandBehavior)
at LinqToDB.Data.CommandInfo.Query[T]()
and
System.NullReferenceException
Object reference not set to an instance of an object.
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior)
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)
at LinqToDB.Data.DataConnection.ExecuteReader(CommandBehavior commandBehavior)
at LinqToDB.Data.CommandInfo.Query[T]()
Steps to reproduce
public void Main(){
var db = new Db();
var timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Elapsed += () => {
db.Get<T>(...);
};
timer.Start();
for(var i = 0; i< 100;i++){
db.Get<T>(...);
}
}
create a DataConnection, and timer, and loop querying from both. Eventually you’ll run into one of these exceptions.
Environment details
linq2db version: 2.7.4 (not sure this matters) Database Server: ? Database Provider: ? Operating system: ? .NET Framework: ?
Issue Analytics
- State:
- Created 4 years ago
- Comments:6 (5 by maintainers)
You SHOULD create data connection for each thread.
DataConnection
is not thread safe.@MaceWindu, i think guard for such improper usage is needed. Maybe in
DataProvider.ExecuteScope()
.I would posit that it’s not thread safe because that is a fairly common paradigm in many data access providers.
As a consideration; let’s say
DataConnection
was threadsafe. What would that mean when it comes to things like Transactions? How would that API work? (It’s certainly DOABLE, but it would look very different and perhaps not clean as one would think at first glance.)That’s a pretty standard pattern in ADO.NET.
Edit; IIRC NHibernate
SessionFactory
is one of the exceptions to this statement, but that is again an exception and not the rule;Session
itself is not thread safe, and you mainly wantSessionFactory
as as singleton because it is relatively expensive.In Linq2Db
DataConnection
is (in my experience) fairly inexpensive, and there is caching in much of the query pipeline where appropriate (which is usually the more expensive part).Edit 2: Added more explanation to code above.