sqlite.net + monotouch = SIGSEGV се срива

Ние използваме следното:

  • 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 като нас.

Неговото решение включва изрично изхвърляне на обекти Command, за да се гарантира, че GC може да се справи и т.н. Когато се опитах да обвия моите обекти Command (от sqlite.net) в клауза using(){}, разбрах, че не са за еднократна употреба.

Опитах да вмъкна 100ms закъснения и това спира сривовете, но това не е жизнеспособно решение за нас.

Има ли някаква надежда за 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.

person Steve Macdonald    schedule 27.06.2014    source източник


Отговори (2)


Почти съм сигурен, че получавах значими грешки вместо тези на SIGSEGV, когато се опитах да създам една и съща sqlite.net връзка от множество нишки, но ако смятате, че това е виновникът, решението е просто: трябва да ограничите достъпа до всеки 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, който гарантира, че ще изпълни двуетапната операция „актуализиране или вмъкване“ атомарно .

person Kirill Shlenskiy    schedule 27.06.2014
comment
Да, някак си клонях към това. Ще опитам това и ще докладвам възможно най-скоро. Ще бъде интересно да се види какво влияние върху производителността, както и стабилността. - person Steve Macdonald; 27.06.2014
comment
@SteveMacdonald, по отношение на стабилността това е почти бронирано, стига да не излагате публично основната връзка или да създавате множество екземпляри на обвивка. Що се отнася до производителността, самите заключвания въвеждат незначителни допълнителни разходи, но очевидно ще получите удар, когато има спор за заключвания. - person Kirill Shlenskiy; 27.06.2014
comment
Кирил -- каквото и да става в моя случай, за съжаление сериализираният достъп чрез обвивката не реши проблема. Моята обвивка е основно същата, както сте посочили, но все още получавам грешките на seg. Когато моят тестов сноп пише само във фонов режим, неизправностите са редки. Когато променя това на смес от четене/запис във фонов режим, веднага се поврежда. Обърнете внимание, че фонът може да работи за неопределено време без проблеми, докато не бъде направена заявка за db на нишката на преден план - само тогава възниква грешка. - person Steve Macdonald; 28.06.2014
comment
@SteveMacdonald, има ли шанс да публикувате своя тестов сбруя - може би като същина? (gist.github.com) - person Kirill Shlenskiy; 28.06.2014
comment
Това наистина засилва интереса ми, защото вашият сценарий е точно това, което имам в един особено огромен проект за iOS - sqlite.net + масово паралелно (!) непрекъснато фоново синхронизиране + комбинация от едновременни задействани операции на базата данни на фона и на преден план чрез действията на потребителя. Няма проблеми с паралелността на базата данни за повече от година в производството, просто никакви. Каквото и да се случва във вашия случай, имам чувството, че проблемът ще бъде изключително прост, дори и да не е веднага очевиден. - person Kirill Shlenskiy; 28.06.2014
comment
Ще трябва да извадя съответните неща и да създам малък сбруя, за да направя това възпроизводимо по лесен начин. Ще се опитам да го направя този уикенд или евентуално понеделник/вторник. Ще публикува веднага щом е готов. Между другото, използвах по-стара версия на sqlite-net и току-що актуализирах до най-новата на NuGet. Изглежда малко по-стабилно, но може да е моето въображение, тъй като сривовете са доста случайни. Във всеки случай частичните подобрения не помагат много. - person Steve Macdonald; 28.06.2014
comment
@SteveMacdonald, добре тогава, очаквам го с нетърпение. Ако има някаква помощ, използвам sqlite-net-1.0.1 от Component Store, надстроен преди известно време от v1.0, също Component Store - също работи добре. - person Kirill Shlenskiy; 28.06.2014
comment
И така, ето още една актуализация: като вас използвах произволен обект като мой обект на шкафче. Все още се разбиваше. След това прочетох още от коментарите в статията, към която дадох връзка във въпроса си. Той използваше обекта sqliteconnection като обект на шкафче. Когато промених кода си, за да използвам самата връзка като заключващ обект, сега не мога да накарам приложението да се срине. Веднъж се изключи, но мисля, че това беше несвързан проблем. Ще продължа да го блъскам известно време, за да видя дали това наистина е решение. Всъщност не съм експерт по .net и AFAIK, използвайки който и да е обект, тъй като обектът за заключване трябва да работи... - person Steve Macdonald; 28.06.2014
comment
Ще отбележа отговора ви като правилен, тъй като изглежда е довел до решението. Като минимум приложението вече е много по-стабилно. - person Steve Macdonald; 28.06.2014
comment
@SteveMacdonald, прав си, като казваш, че използването на (почти) всеки обект като ключалка трябва да работи и в този момент имам много малко съмнения, че в крайна сметка си създал множество екземпляри на ключалка, водещи до не изключително заключване в края. т.е. ако създадете няколко екземпляра на вашата обвивка, като всички обвиват един и същ SQLiteConnection, всеки ще има свой собствен обект за заключване и няма да успее да ограничи достъпа до основната връзка. Парите ми са за факта, че използването на старата ви заключваща логика и промяната на заключващия обект на static readonly би дало същия резултат. - person Kirill Shlenskiy; 28.06.2014

Опитайте да добавите флаговете: SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.FullMutex към вашия конструктор на SQLite връзка. Реши проблема ни. Изглежда, че SQLite все още върши някаква фонова работа след транзакции, като използването на вътрешния мютекс гарантира основната последователност.

person Brizio    schedule 23.01.2017