We're using the following:
The app synchronizes data with a server on a background thread. There is only one SQLite connection object shared by the entire app. Foreground queries are executed at the same time the background sync is running. All of this has worked fine on a Windows 8.1 version of the app (i.e., on MSFT Surface and similar). However once we switched to Xamarin/mono we started getting constant crashes as shown below.
Research led to this article: http://www.aaronheise.com/2012/12/monotouch-sqlite-sigsegv/
He's using using Mono.Data.SqliteClient, not sqlite.net as we are.
His solution involves explicitly disposing of Command objects in order to ensure the GC can keep up etc. When I tried to wrap my Command objects (from sqlite.net) in a using(){} clause I found out they are not disposable.
I've tried inserting 100ms delays and that stops the crashes, however it's not a viable solution for us.
Is there any hope for sqlite.net here, or should I look for a different way to use sqlite?
mono-rt: Stacktrace:
mono-rt: at <unknown> <0xffffffff>
mono-rt: at (wrapper managed-to-native) SQLite.SQLite3.Prepare2 (intptr,string,int,intptr&,intptr) <IL 0x0003c, 0xffffffff>
...
mono-rt:
Native stacktrace:
mono-rt:
Got a SIGSEGV while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
I'm pretty sure I was getting meaningful errors instead of SIGSEGV's when I tried hammering the same sqlite.net connection from multiple threads, but if you believe that that's the culprit, the solution is simple: you need to restrict access to any sqlite.net methods which touch the database to one thread at a time.
In the scenario where you're sharing a single SQLiteConnection
instance in your app (which is a perfectly valid way of doing things), I recommend creating a simplified proxy class wrapping your sqlite.net connection, exposing only the methods that you want and protecting access to those with lock
statements, i.e:
public class DatabaseWrapper : IDisposable
{
// Fields.
private readonly SQLiteConnection Connection;
private readonly object Lock = new object();
public DatabaseWrapper(string databasePath)
{
if (string.IsNullOrEmpty(databasePath)) throw new ArgumentException("Database path cannot be null or empty.");
this.Connection = new SQLiteConnection(databasePath);
}
public IEnumerable<T> Entities<T>() where T : new()
{
lock (this.Lock)
{
return this.Connection.Table<T>();
}
}
public IEnumerable<T> Query<T>(string query, params object[] args) where T : new()
{
lock (this.Lock)
{
return this.Connection.Query<T>(query, args);
}
}
public int ExecuteNonQuery(string sql, params object[] args)
{
lock (this.Lock)
{
return this.Connection.Execute(sql, args);
}
}
public T ExecuteScalar<T>(string sql, params object[] args)
{
lock (this.Lock)
{
return this.Connection.ExecuteScalar<T>(sql, args);
}
}
public void Insert<T>(T entity)
{
lock (this.Lock)
{
this.Connection.Insert(entity);
}
}
public void Update<T>(T entity)
{
lock (this.Lock)
{
this.Connection.Update(entity);
}
}
public void Upsert<T>(T entity)
{
lock (this.Lock)
{
var rowCount = this.Connection.Update(entity);
if (rowCount == 0)
{
this.Connection.Insert(entity);
}
}
}
public void Delete<T>(T entity)
{
lock (this.Lock)
{
this.Connection.Delete(entity);
}
}
public void Dispose()
{
this.Connection.Dispose();
}
}
P.S. Obviously since you're doing things on multiple threads you need to be very careful not to introduce race conditions, which is why, for example, I included the Upsert
method that is guaranteed to perform the two-step "update or insert" operation atomically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With