sqlite.net与monotouch合并会导致SIGSEGV崩溃问题

5
我们正在使用以下技术:
  • Xamarin 3 (Xamarin Forms)
  • MonoTouch
  • sqlite.net
  • iOS模拟器/硬件
该应用程序在后台线程上与服务器同步数据。整个应用程序只有一个SQLite连接对象。前台查询与后台同步同时执行。在Windows 8.1版本的应用程序上(例如在MSFT Surface等设备上),所有这些都可以正常工作。然而,一旦我们切换到Xamarin / mono,我们开始不断遇到如下崩溃。
研究后发现了这篇文章:http://www.aaronheise.com/2012/12/monotouch-sqlite-sigsegv/,他使用的是Mono.Data.SqliteClient,而不是我们使用的sqlite.net。
他的解决方案涉及显式处置命令对象,以确保GC可以跟上等等。当我尝试将我的Command对象(来自sqlite.net)包装在using(){}子句中时,我发现它们不能被处理。
我已经尝试插入100毫秒的延迟,这可以防止崩溃,但对我们来说不是可行的解决方案。
在这里,sqlite.net有希望吗?还是我应该寻找其他使用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.
2个回答

4
我相信当我尝试从多个线程同时攻击同一个sqlite.net连接时,我得到的是有意义的错误而不是SIGSEGV,但如果你认为这是罪魁祸首,解决方法很简单:你需要限制任何触及数据库的sqlite.net方法仅在一个线程中访问。
在您的应用程序中共享单个SQLiteConnection实例的情况下(这是一种完全有效的做法),我推荐创建一个简化的代理类来包装您的sqlite.net连接,仅公开您需要的方法,并通过lock语句保护对其的访问,例如:
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. 显然,由于您正在多个线程上执行操作,因此需要非常小心,以免引入竞态条件。为此,例如,我包括了Upsert方法,该方法保证原子地执行两步“更新或插入”操作。


是的,我有点倾向于这个方向。我会尝试并尽快回报。很有趣的是看看对性能和稳定性有什么影响。 - Steve Macdonald
@SteveMacdonald,就稳定性而言,只要不公开暴露底层连接或创建多个包装实例,这基本上是非常牢固的。至于性能方面,锁本身引入的开销微不足道,但当存在锁争用时,显然会受到影响。 - Kirill Shlenskiy
Kirill - 不管在我的情况下发生了什么,不幸的是通过包装器进行序列化访问并没有解决问题。我的包装器基本上与您指示的相同,但我仍然遇到段错误。当我的测试工具只在后台执行写操作时,故障不经常发生。当我将其更改为在后台执行读/写混合操作时,它会立即出现故障。请注意,后台可以无限期地运行而不会出现问题,直到在前台线程上进行数据库请求 - 然后才会出现故障。 - Steve Macdonald
我需要提取相关内容并构建一个小工具,以便简单地进行再现。我会尝试在这个周末或可能是周一/周二完成它。准备好后会立即发布。顺便说一下,我之前使用的是较旧版本的sqlite-net,现在已经更新到NuGet上的最新版本。它似乎更加稳定,但崩溃仍然很随机。无论如何,部分改进并没有太大帮助。 - Steve Macdonald
我将把你的答案标记为正确,因为它似乎已经导致了解决方案。至少现在应用程序更加稳定。 - Steve Macdonald
显示剩余5条评论

0
尝试在SQLite连接构造函数中添加标志:SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.FullMutex。这解决了我们的问题。看起来在事务之后,SQLite仍然会进行一些后台工作,使用内部互斥锁可以确保基本一致性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接