Skip to content

Commit cb4fe6c

Browse files
committed
Added database file bases lock
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)
1 parent ec7b882 commit cb4fe6c

File tree

1 file changed

+93
-48
lines changed

1 file changed

+93
-48
lines changed

SQLite4Unity3d/SQLite4Unity3d/SQLite.cs

Lines changed: 93 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,21 @@ public partial class SQLiteConnection : IDisposable
140140

141141
public string DatabasePath { get; private set; }
142142

143+
// Dictionary of synchronization objects.
144+
//
145+
// To prevent database disruption, a database file must be accessed *synchronously*.
146+
// For the purpose we create synchronous objects for each database file and store in the
147+
// static dictionary to share it among all connections.
148+
// The key of the dictionary is database file path and its value is an object to be used
149+
// by lock() statement.
150+
//
151+
// Use case:
152+
// - database file lock is done implicitly and automatically.
153+
// - To prepend deadlock, application may lock a database file explicity by either way:
154+
// - RunInTransaction(Action) locks the database during the transaction (for insert/update)
155+
// - RunInDatabaseLock(Action) similarly locks the database but no transaction (for query)
156+
private static Dictionary<string, object> syncObjects = new Dictionary<string, object>();
157+
143158
public bool TimeExecution { get; set; }
144159

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

197212
DatabasePath = databasePath;
213+
mayCreateSyncObject(databasePath);
198214

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

250+
void mayCreateSyncObject(string databasePath)
251+
{
252+
if (!syncObjects.ContainsKey(databasePath)) {
253+
syncObjects[databasePath] = new object();
254+
}
255+
}
256+
257+
/// <summary>
258+
/// Gets the synchronous object, to be lock the database file for updating.
259+
/// </summary>
260+
/// <value>The sync object.</value>
261+
public object SyncObject { get { return syncObjects[DatabasePath];} }
262+
234263
public void EnableLoadExtension(int onoff)
235264
{
236265
SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff);
@@ -1048,15 +1077,30 @@ public void Commit ()
10481077
public void RunInTransaction (Action action)
10491078
{
10501079
try {
1051-
var savePoint = SaveTransactionPoint ();
1052-
action ();
1053-
Release (savePoint);
1080+
lock (syncObjects[DatabasePath]) {
1081+
var savePoint = SaveTransactionPoint ();
1082+
action ();
1083+
Release (savePoint);
1084+
}
10541085
} catch (Exception) {
10551086
Rollback ();
10561087
throw;
10571088
}
10581089
}
10591090

1091+
/// <summary>
1092+
/// Executes <param name="action"> while blocking other threads to access the same database.
1093+
/// </summary>
1094+
/// <param name="action">
1095+
/// The <see cref="Action"/> to perform within a lock.
1096+
/// </param>
1097+
public void RunInDatabaseLock (Action action)
1098+
{
1099+
lock (syncObjects[DatabasePath]) {
1100+
action ();
1101+
}
1102+
}
1103+
10601104
/// <summary>
10611105
/// Inserts all specified objects.
10621106
/// </summary>
@@ -2000,9 +2044,12 @@ public int ExecuteNonQuery ()
20002044
}
20012045

20022046
var r = SQLite3.Result.OK;
2003-
var stmt = Prepare ();
2004-
r = SQLite3.Step (stmt);
2005-
Finalize (stmt);
2047+
lock (_conn.SyncObject) {
2048+
var stmt = Prepare ();
2049+
r = SQLite3.Step (stmt);
2050+
Finalize(stmt);
2051+
}
2052+
20062053
if (r == SQLite3.Result.Done) {
20072054
int rowsAffected = SQLite3.Changes (_conn.Handle);
20082055
return rowsAffected;
@@ -2057,33 +2104,32 @@ public IEnumerable<T> ExecuteDeferredQuery<T> (TableMapping map)
20572104
_conn.InvokeTrace ("Executing Query: " + this);
20582105
}
20592106

2060-
var stmt = Prepare ();
2061-
try
2062-
{
2063-
var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)];
2107+
lock (_conn.SyncObject) {
2108+
var stmt = Prepare ();
2109+
try {
2110+
var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)];
20642111

2065-
for (int i = 0; i < cols.Length; i++) {
2066-
var name = SQLite3.ColumnName16 (stmt, i);
2067-
cols [i] = map.FindColumn (name);
2068-
}
2069-
2070-
while (SQLite3.Step (stmt) == SQLite3.Result.Row) {
2071-
var obj = Activator.CreateInstance(map.MappedType);
20722112
for (int i = 0; i < cols.Length; i++) {
2073-
if (cols [i] == null)
2074-
continue;
2075-
var colType = SQLite3.ColumnType (stmt, i);
2076-
var val = ReadCol (stmt, i, colType, cols [i].ColumnType);
2077-
cols [i].SetValue (obj, val);
2078-
}
2079-
OnInstanceCreated (obj);
2080-
yield return (T)obj;
2113+
var name = SQLite3.ColumnName16 (stmt, i);
2114+
cols [i] = map.FindColumn (name);
2115+
}
2116+
2117+
while (SQLite3.Step (stmt) == SQLite3.Result.Row) {
2118+
var obj = Activator.CreateInstance(map.MappedType);
2119+
for (int i = 0; i < cols.Length; i++) {
2120+
if (cols [i] == null)
2121+
continue;
2122+
var colType = SQLite3.ColumnType (stmt, i);
2123+
var val = ReadCol (stmt, i, colType, cols [i].ColumnType);
2124+
cols [i].SetValue (obj, val);
2125+
}
2126+
OnInstanceCreated (obj);
2127+
yield return (T)obj;
2128+
}
2129+
} finally {
2130+
SQLite3.Finalize(stmt);
20812131
}
20822132
}
2083-
finally
2084-
{
2085-
SQLite3.Finalize(stmt);
2086-
}
20872133
}
20882134

20892135
public T ExecuteScalar<T> ()
@@ -2094,26 +2140,25 @@ public T ExecuteScalar<T> ()
20942140

20952141
T val = default(T);
20962142

2097-
var stmt = Prepare ();
2143+
lock (_conn.SyncObject) {
2144+
var stmt = Prepare();
20982145

2099-
try
2100-
{
2101-
var r = SQLite3.Step (stmt);
2102-
if (r == SQLite3.Result.Row) {
2103-
var colType = SQLite3.ColumnType (stmt, 0);
2104-
val = (T)ReadCol (stmt, 0, colType, typeof(T));
2105-
}
2106-
else if (r == SQLite3.Result.Done) {
2107-
}
2108-
else
2109-
{
2110-
throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle));
2111-
}
2112-
}
2113-
finally
2114-
{
2115-
Finalize (stmt);
2116-
}
2146+
try {
2147+
var r = SQLite3.Step (stmt);
2148+
if (r == SQLite3.Result.Row) {
2149+
var colType = SQLite3.ColumnType (stmt, 0);
2150+
val = (T)ReadCol (stmt, 0, colType, typeof(T));
2151+
}
2152+
else if (r == SQLite3.Result.Done) {
2153+
}
2154+
else
2155+
{
2156+
throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle));
2157+
}
2158+
} finally {
2159+
Finalize (stmt);
2160+
}
2161+
}
21172162

21182163
return val;
21192164
}

0 commit comments

Comments
 (0)