5

We're using the following:

  • Xamarin 3 (Xamarin Forms)
  • MonoTouch
  • sqlite.net
  • iOS simulator/hardware

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.
Steve Macdonald
  • 1,745
  • 2
  • 20
  • 34

2 Answers2

4

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.

Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • Yeah I was sort of leaning that way. I'll try this and report back ASAP. Will be interesting to see what impact on performance, as well as stability. – Steve Macdonald Jun 27 '14 at 14:52
  • @SteveMacdonald, in terms of stability this is pretty much bulletproof as long as you're not publicly exposing the underlying connection or creating multiple wrapper instances. As for performance, the locks themselves introduce negligible overhead, but you'll obviously take a hit when there is lock contention. – Kirill Shlenskiy Jun 27 '14 at 15:03
  • Kirill -- well whatever is going on in my case, unfortunately the serialized access via the wrapper did not solve the problem. My wrapper is basically the same as you've indicated, but I still get the seg faults. When my test harness only does writes in the background, the faults are infrequent. When I change that to a mixture of reads/writes on background, it faults immediately. Note that the background can run indefinitely without problems until a db request is made on the foreground thread -- only then does it fault. – Steve Macdonald Jun 28 '14 at 11:43
  • @SteveMacdonald, any chance you can post your test harness - perhaps as a gist? (https://gist.github.com/) – Kirill Shlenskiy Jun 28 '14 at 11:47
  • This is really peaking my interest because your scenario is *exactly* what I have in one particularly huge iOS project - sqlite.net + massively parallel (!) continuous background sync + mix of concurrent background and foreground database operations triggered by user actions. No database concurrency issues in over a year in production, just none. Whatever is happening in your case, I have a feeling that the problem will be extremely simple even if it's not immediately obvious. – Kirill Shlenskiy Jun 28 '14 at 11:55
  • I'm going to have to extract out the relevant stuff and build a little harness to make this reproducible in a simple way. I'll try to get that done this weekend, or possibly Monday/Tuesday. Will post as soon as ready. BTW, I was using an older version of sqlite-net, and I just updated to latest on NuGet. It *seems* a bit more stable, but could be my imagination as crashes are quite random. In any case partial improvements don't help much. – Steve Macdonald Jun 28 '14 at 12:53
  • @SteveMacdonald, alright then, looking forward to it. If it's any help, I'm using sqlite-net-1.0.1 from the Component Store, upgraded a while ago from v1.0, also Component Store - was working fine too. – Kirill Shlenskiy Jun 28 '14 at 13:06
  • So here's another update: like you I was using an arbitrary object as my "locker" object. It was still crashing. Then I read more of the comments in the article I linked to in my question. He was using the sqliteconnection object as his "locker" object. When I changed my code to use the connection itself as the lock object, I'm now unable to get the app to crash. It did shut down once but I think that was an unrelated issue. I'll keep hammering on it for a while to see if this really is a solution. I'm not really a .net expert and AFAIK using any object as the lock object should work... – Steve Macdonald Jun 28 '14 at 13:48
  • I'm going to mark your answer as correct since it seems to have led to the solution. At minimum the app is far more stable now. – Steve Macdonald Jun 28 '14 at 13:52
  • @SteveMacdonald, you are correct in saying that using (almost) any object as the lock has to work, and at this point I have very little doubt left that you ended up creating multiple lock instances leading to *no* exclusive locking in the end. I.e. if you create multiple instances of your wrapper all wrapping the same `SQLiteConnection`, each will have their own lock object and will fail to restrict access to the underlying connection. My money's on the fact that using your old locking logic and changing the lock object to be `static readonly` would have produced the same result. – Kirill Shlenskiy Jun 28 '14 at 13:56
0

Try adding the flags: SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.FullMutex to your SQLite connection constructor. Solved our problem. Looks like SQLite still does some background work after transactions, using the internal mutex ensures the base consistency.

Brizio
  • 31
  • 1
  • 7