Skip to content

Commit

Permalink
Added database file bases lock
Browse files Browse the repository at this point in the history
Current version of sqlite.so(~3.8.5) it seems that it needs to lock
entire a database file during SQL access.
- not only row nor table but a file
- not only INSERT, UPDATE but also SELECT

Without lock, app will have either:
- database file disruption
- BUSY exception with much time overhead

To ensure that, I put logic around SQL execution to lock entire file
automatically.

And to prepend deadlock I added more:
- RunInTransaction(Action) locks the database during the transaction (for insert/update)
- RunInDatabaseLock(Action) similarly locks the database but no transaction (for query)
  • Loading branch information
pokehanai committed Nov 18, 2015
1 parent ec7b882 commit cb4fe6c
Showing 1 changed file with 93 additions and 48 deletions.
141 changes: 93 additions & 48 deletions SQLite4Unity3d/SQLite4Unity3d/SQLite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ public partial class SQLiteConnection : IDisposable

public string DatabasePath { get; private set; }

// Dictionary of synchronization objects.
//
// To prevent database disruption, a database file must be accessed *synchronously*.
// For the purpose we create synchronous objects for each database file and store in the
// static dictionary to share it among all connections.
// The key of the dictionary is database file path and its value is an object to be used
// by lock() statement.
//
// Use case:
// - database file lock is done implicitly and automatically.
// - To prepend deadlock, application may lock a database file explicity by either way:
// - RunInTransaction(Action) locks the database during the transaction (for insert/update)
// - RunInDatabaseLock(Action) similarly locks the database but no transaction (for query)
private static Dictionary<string, object> syncObjects = new Dictionary<string, object>();

public bool TimeExecution { get; set; }

#region debug tracing
Expand Down Expand Up @@ -195,6 +210,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st
throw new ArgumentException ("Must be specified", "databasePath");

DatabasePath = databasePath;
mayCreateSyncObject(databasePath);

#if NETFX_CORE
SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path);
Expand Down Expand Up @@ -231,6 +247,19 @@ static SQLiteConnection ()
}
}

void mayCreateSyncObject(string databasePath)
{
if (!syncObjects.ContainsKey(databasePath)) {
syncObjects[databasePath] = new object();
}
}

/// <summary>
/// Gets the synchronous object, to be lock the database file for updating.
/// </summary>
/// <value>The sync object.</value>
public object SyncObject { get { return syncObjects[DatabasePath];} }

public void EnableLoadExtension(int onoff)
{
SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff);
Expand Down Expand Up @@ -1048,15 +1077,30 @@ public void Commit ()
public void RunInTransaction (Action action)
{
try {
var savePoint = SaveTransactionPoint ();
action ();
Release (savePoint);
lock (syncObjects[DatabasePath]) {
var savePoint = SaveTransactionPoint ();
action ();
Release (savePoint);
}
} catch (Exception) {
Rollback ();
throw;
}
}

/// <summary>
/// Executes <param name="action"> while blocking other threads to access the same database.
/// </summary>
/// <param name="action">
/// The <see cref="Action"/> to perform within a lock.
/// </param>
public void RunInDatabaseLock (Action action)
{
lock (syncObjects[DatabasePath]) {
action ();
}
}

/// <summary>
/// Inserts all specified objects.
/// </summary>
Expand Down Expand Up @@ -2000,9 +2044,12 @@ public int ExecuteNonQuery ()
}

var r = SQLite3.Result.OK;
var stmt = Prepare ();
r = SQLite3.Step (stmt);
Finalize (stmt);
lock (_conn.SyncObject) {
var stmt = Prepare ();
r = SQLite3.Step (stmt);
Finalize(stmt);
}

if (r == SQLite3.Result.Done) {
int rowsAffected = SQLite3.Changes (_conn.Handle);
return rowsAffected;
Expand Down Expand Up @@ -2057,33 +2104,32 @@ public IEnumerable<T> ExecuteDeferredQuery<T> (TableMapping map)
_conn.InvokeTrace ("Executing Query: " + this);
}

var stmt = Prepare ();
try
{
var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)];
lock (_conn.SyncObject) {
var stmt = Prepare ();
try {
var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)];

for (int i = 0; i < cols.Length; i++) {
var name = SQLite3.ColumnName16 (stmt, i);
cols [i] = map.FindColumn (name);
}

while (SQLite3.Step (stmt) == SQLite3.Result.Row) {
var obj = Activator.CreateInstance(map.MappedType);
for (int i = 0; i < cols.Length; i++) {
if (cols [i] == null)
continue;
var colType = SQLite3.ColumnType (stmt, i);
var val = ReadCol (stmt, i, colType, cols [i].ColumnType);
cols [i].SetValue (obj, val);
}
OnInstanceCreated (obj);
yield return (T)obj;
var name = SQLite3.ColumnName16 (stmt, i);
cols [i] = map.FindColumn (name);
}

while (SQLite3.Step (stmt) == SQLite3.Result.Row) {
var obj = Activator.CreateInstance(map.MappedType);
for (int i = 0; i < cols.Length; i++) {
if (cols [i] == null)
continue;
var colType = SQLite3.ColumnType (stmt, i);
var val = ReadCol (stmt, i, colType, cols [i].ColumnType);
cols [i].SetValue (obj, val);
}
OnInstanceCreated (obj);
yield return (T)obj;
}
} finally {
SQLite3.Finalize(stmt);
}
}
finally
{
SQLite3.Finalize(stmt);
}
}

public T ExecuteScalar<T> ()
Expand All @@ -2094,26 +2140,25 @@ public T ExecuteScalar<T> ()

T val = default(T);

var stmt = Prepare ();
lock (_conn.SyncObject) {
var stmt = Prepare();

try
{
var r = SQLite3.Step (stmt);
if (r == SQLite3.Result.Row) {
var colType = SQLite3.ColumnType (stmt, 0);
val = (T)ReadCol (stmt, 0, colType, typeof(T));
}
else if (r == SQLite3.Result.Done) {
}
else
{
throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle));
}
}
finally
{
Finalize (stmt);
}
try {
var r = SQLite3.Step (stmt);
if (r == SQLite3.Result.Row) {
var colType = SQLite3.ColumnType (stmt, 0);
val = (T)ReadCol (stmt, 0, colType, typeof(T));
}
else if (r == SQLite3.Result.Done) {
}
else
{
throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle));
}
} finally {
Finalize (stmt);
}
}

return val;
}
Expand Down

0 comments on commit cb4fe6c

Please sign in to comment.