Skip to content

Commit 5863a69

Browse files
Merge pull request #511 from ParsePlatform/richardross.localdatastore.sqlite.threadsafe
Made all accesses to PFSQLiteDatabaseResult & Statement thread-safe.
2 parents 62c7efb + 9757503 commit 5863a69

File tree

6 files changed

+110
-62
lines changed

6 files changed

+110
-62
lines changed

Parse/Internal/LocalDataStore/SQLite/PFSQLiteDatabase.m

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#import "PFSQLiteDatabaseResult.h"
2424
#import "PFSQLiteStatement.h"
2525
#import "Parse_Private.h"
26+
#import "PFThreadsafety.h"
2627

2728
static NSString *const PFSQLiteDatabaseBeginExclusiveOperationCommand = @"BEGIN EXCLUSIVE";
2829
static NSString *const PFSQLiteDatabaseCommitOperationCommand = @"COMMIT";
@@ -67,8 +68,16 @@ - (instancetype)initWithPath:(NSString *)path {
6768

6869
_databaseClosedTaskCompletionSource = [[BFTaskCompletionSource alloc] init];
6970
_databasePath = [path copy];
70-
_databaseQueue = dispatch_queue_create("com.parse.sqlite.db.queue", DISPATCH_QUEUE_SERIAL);
71-
_databaseExecutor = [BFExecutor executorWithDispatchQueue:_databaseQueue];
71+
72+
_databaseQueue = PFThreadsafetyCreateQueueForObject(self);
73+
_databaseExecutor = [BFExecutor executorWithBlock:^(dispatch_block_t block) {
74+
// Execute asynchrounously on the proper queue.
75+
// Seems a bit backwards, but we don't have PFThreadsafetySafeDispatchAsync.
76+
dispatch_async(dispatch_get_global_queue(0, 0), ^{
77+
PFThreadsafetySafeDispatchSync(_databaseQueue, block);
78+
});
79+
}];
80+
7281
_cachedStatements = [[NSMutableDictionary alloc] init];
7382

7483
return self;
@@ -178,7 +187,7 @@ - (BFTask *)_executeQueryAsync:(NSString *)sql withArgumentsInArray:(NSArray *)a
178187
sqlite3_finalize(sqliteStatement);
179188
return [BFTask taskWithError:[self _errorWithErrorCode:resultCode]];
180189
}
181-
statement = [[PFSQLiteStatement alloc] initWithStatement:sqliteStatement];
190+
statement = [[PFSQLiteStatement alloc] initWithStatement:sqliteStatement queue:_databaseQueue];
182191

183192
if (enableCaching) {
184193
[self _cacheStatement:statement forQuery:sql];
@@ -206,7 +215,7 @@ - (BFTask *)_executeQueryAsync:(NSString *)sql withArgumentsInArray:(NSArray *)a
206215
[self _bindObject:args[idx] toColumn:(idx + 1) inStatement:statement];
207216
}
208217

209-
PFSQLiteDatabaseResult *result = [[PFSQLiteDatabaseResult alloc] initWithStatement:statement];
218+
PFSQLiteDatabaseResult *result = [[PFSQLiteDatabaseResult alloc] initWithStatement:statement queue:_databaseQueue];
210219
return [BFTask taskWithResult:result];
211220
}
212221

Parse/Internal/LocalDataStore/SQLite/PFSQLiteDatabaseResult.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
1515

1616
@interface PFSQLiteDatabaseResult : NSObject
1717

18-
- (instancetype)initWithStatement:(PFSQLiteStatement *)statement;
18+
- (instancetype)initWithStatement:(PFSQLiteStatement *)statement queue:(dispatch_queue_t)queue;
1919

2020
/*!
2121
Move current result to next row. Returns true if next result exists. False if current result

Parse/Internal/LocalDataStore/SQLite/PFSQLiteDatabaseResult.m

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,24 @@
1212
#import <sqlite3.h>
1313

1414
#import "PFSQLiteStatement.h"
15+
#import "PFThreadsafety.h"
1516

1617
@interface PFSQLiteDatabaseResult ()
1718

1819
@property (nonatomic, copy, readonly) NSDictionary *columnNameToIndexMap;
1920
@property (nonatomic, strong, readonly) PFSQLiteStatement *statement;
21+
@property (nonatomic, strong, readonly) dispatch_queue_t databaseQueue;
2022

2123
@end
2224

2325
@implementation PFSQLiteDatabaseResult
2426

2527
@synthesize columnNameToIndexMap = _columnNameToIndexMap;
2628

27-
- (instancetype)initWithStatement:(PFSQLiteStatement *)stmt {
29+
- (instancetype)initWithStatement:(PFSQLiteStatement *)stmt queue:(dispatch_queue_t)queue {
2830
if ((self = [super init])) {
2931
_statement = stmt;
32+
_databaseQueue = queue;
3033
}
3134
return self;
3235
}
@@ -36,7 +39,9 @@ - (BOOL)next {
3639
}
3740

3841
- (int)step {
39-
return sqlite3_step([self.statement sqliteStatement]);
42+
return PFThreadSafetyPerform(_databaseQueue, ^{
43+
return sqlite3_step([self.statement sqliteStatement]);
44+
});
4045
}
4146

4247
- (BOOL)close {
@@ -48,47 +53,57 @@ - (int)intForColumn:(NSString *)columnName {
4853
}
4954

5055
- (int)intForColumnIndex:(int)columnIndex {
51-
return sqlite3_column_int([self.statement sqliteStatement], columnIndex);
56+
return PFThreadSafetyPerform(_databaseQueue, ^{
57+
return sqlite3_column_int([self.statement sqliteStatement], columnIndex);
58+
});
5259
}
5360

5461
- (long)longForColumn:(NSString *)columnName {
5562
return [self longForColumnIndex:[self columnIndexForName:columnName]];
5663
}
5764

5865
- (long)longForColumnIndex:(int)columnIndex {
59-
return (long)sqlite3_column_int64([self.statement sqliteStatement], columnIndex);
66+
return PFThreadSafetyPerform(_databaseQueue, ^{
67+
return (long)sqlite3_column_int64([self.statement sqliteStatement], columnIndex);
68+
});
6069
}
6170

6271
- (BOOL)boolForColumn:(NSString *)columnName {
6372
return [self boolForColumnIndex:[self columnIndexForName:columnName]];
6473
}
6574

6675
- (BOOL)boolForColumnIndex:(int)columnIndex {
67-
return ([self intForColumnIndex:columnIndex] != 0);
76+
return PFThreadSafetyPerform(_databaseQueue, ^{
77+
return ([self intForColumnIndex:columnIndex] != 0);
78+
});
6879
}
6980

7081
- (double)doubleForColumn:(NSString *)columnName {
7182
return [self doubleForColumnIndex:[self columnIndexForName:columnName]];
7283
}
7384

7485
- (double)doubleForColumnIndex:(int)columnIndex {
75-
return sqlite3_column_double([self.statement sqliteStatement], columnIndex);
86+
return PFThreadSafetyPerform(_databaseQueue, ^{
87+
return sqlite3_column_double([self.statement sqliteStatement], columnIndex);
88+
});
7689
}
7790

7891
- (NSString *)stringForColumn:(NSString *)columnName {
7992
return [self stringForColumnIndex:[self columnIndexForName:columnName]];
8093
}
8194

8295
- (NSString *)stringForColumnIndex:(int)columnIndex {
83-
if ([self columnIndexIsNull:columnIndex]) {
84-
return nil;
85-
}
96+
return PFThreadSafetyPerform(_databaseQueue, ^NSString *{
97+
if ([self columnIndexIsNull:columnIndex]) {
98+
return nil;
99+
}
86100

87-
const char *str = (const char *)sqlite3_column_text([self.statement sqliteStatement], columnIndex);
88-
if (!str) {
89-
return nil;
90-
}
91-
return [NSString stringWithUTF8String:str];
101+
const char *str = (const char *)sqlite3_column_text([self.statement sqliteStatement], columnIndex);
102+
if (!str) {
103+
return nil;
104+
}
105+
return [NSString stringWithUTF8String:str];
106+
});
92107
}
93108

94109
- (NSDate *)dateForColumn:(NSString *)columnName {
@@ -105,42 +120,48 @@ - (NSData *)dataForColumn:(NSString *)columnName {
105120
}
106121

107122
- (NSData *)dataForColumnIndex:(int)columnIndex {
108-
if ([self columnIndexIsNull:columnIndex]) {
109-
return nil;
110-
}
123+
return PFThreadSafetyPerform(_databaseQueue, ^NSData *{
124+
if ([self columnIndexIsNull:columnIndex]) {
125+
return nil;
126+
}
111127

112-
int size = sqlite3_column_bytes([self.statement sqliteStatement], columnIndex);
113-
const char *buffer = sqlite3_column_blob([self.statement sqliteStatement], columnIndex);
114-
if (buffer == nil) {
115-
return nil;
116-
}
117-
return [NSData dataWithBytes:buffer length:size];
128+
int size = sqlite3_column_bytes([self.statement sqliteStatement], columnIndex);
129+
const char *buffer = sqlite3_column_blob([self.statement sqliteStatement], columnIndex);
130+
if (buffer == nil) {
131+
return nil;
132+
}
133+
return [NSData dataWithBytes:buffer length:size];
134+
});
118135
}
119136

120137
- (id)objectForColumn:(NSString *)columnName {
121138
return [self objectForColumnIndex:[self columnIndexForName:columnName]];
122139
}
123140

124141
- (id)objectForColumnIndex:(int)columnIndex {
125-
int columnType = sqlite3_column_type([self.statement sqliteStatement], columnIndex);
126-
switch (columnType) {
127-
case SQLITE_INTEGER:
128-
return @([self longForColumnIndex:columnIndex]);
129-
case SQLITE_FLOAT:
130-
return @([self doubleForColumnIndex:columnIndex]);
131-
case SQLITE_BLOB:
132-
return [self dataForColumnIndex:columnIndex];
133-
default:
134-
return [self stringForColumnIndex:columnIndex];
135-
}
142+
return PFThreadSafetyPerform(_databaseQueue, ^id{
143+
int columnType = sqlite3_column_type([self.statement sqliteStatement], columnIndex);
144+
switch (columnType) {
145+
case SQLITE_INTEGER:
146+
return @([self longForColumnIndex:columnIndex]);
147+
case SQLITE_FLOAT:
148+
return @([self doubleForColumnIndex:columnIndex]);
149+
case SQLITE_BLOB:
150+
return [self dataForColumnIndex:columnIndex];
151+
default:
152+
return [self stringForColumnIndex:columnIndex];
153+
}
154+
});
136155
}
137156

138157
- (BOOL)columnIsNull:(NSString *)columnName {
139158
return [self columnIndexIsNull:[self columnIndexForName:columnName]];
140159
}
141160

142161
- (BOOL)columnIndexIsNull:(int)columnIndex {
143-
return (sqlite3_column_type([self.statement sqliteStatement], columnIndex) == SQLITE_NULL);
162+
return PFThreadSafetyPerform(_databaseQueue, ^{
163+
return (sqlite3_column_type([self.statement sqliteStatement], columnIndex) == SQLITE_NULL);
164+
});
144165
}
145166

146167
- (int)columnIndexForName:(NSString *)columnName {
@@ -154,13 +175,15 @@ - (int)columnIndexForName:(NSString *)columnName {
154175

155176
- (NSDictionary *)columnNameToIndexMap {
156177
if (!_columnNameToIndexMap) {
157-
int columnCount = sqlite3_column_count([self.statement sqliteStatement]);
158-
NSMutableDictionary *mutableColumnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:columnCount];
159-
for (int i = 0; i < columnCount; ++i) {
160-
NSString *key = [NSString stringWithUTF8String:sqlite3_column_name([self.statement sqliteStatement], i)];
161-
mutableColumnNameToIndexMap[[key lowercaseString]] = @(i);
162-
}
163-
_columnNameToIndexMap = mutableColumnNameToIndexMap;
178+
PFThreadsafetySafeDispatchSync(_databaseQueue, ^{
179+
int columnCount = sqlite3_column_count([self.statement sqliteStatement]);
180+
NSMutableDictionary *mutableColumnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:columnCount];
181+
for (int i = 0; i < columnCount; ++i) {
182+
NSString *key = [NSString stringWithUTF8String:sqlite3_column_name([self.statement sqliteStatement], i)];
183+
mutableColumnNameToIndexMap[[key lowercaseString]] = @(i);
184+
}
185+
_columnNameToIndexMap = mutableColumnNameToIndexMap;
186+
});
164187
}
165188
return _columnNameToIndexMap;
166189
}

Parse/Internal/LocalDataStore/SQLite/PFSQLiteStatement.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ typedef struct sqlite3_stmt sqlite3_stmt;
1818

1919
@interface PFSQLiteStatement : NSObject
2020

21-
@property (atomic, assign, readonly) sqlite3_stmt *sqliteStatement;
21+
@property (nonatomic, assign, readonly) sqlite3_stmt *sqliteStatement;
22+
@property (nonatomic, strong, readonly) dispatch_queue_t databaseQueue;
2223

23-
- (instancetype)initWithStatement:(sqlite3_stmt *)stmt;
24+
- (instancetype)initWithStatement:(sqlite3_stmt *)stmt queue:(dispatch_queue_t)databaseQueue;
2425

2526
- (BOOL)close;
2627
- (BOOL)reset;

Parse/Internal/LocalDataStore/SQLite/PFSQLiteStatement.m

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111

1212
#import <sqlite3.h>
1313

14+
#import "PFThreadsafety.h"
15+
1416
@implementation PFSQLiteStatement
1517

16-
- (instancetype)initWithStatement:(sqlite3_stmt *)stmt {
18+
- (instancetype)initWithStatement:(sqlite3_stmt *)stmt queue:(dispatch_queue_t)databaseQueue {
1719
self = [super init];
1820
if (!stmt || !self) return nil;
1921

2022
_sqliteStatement = stmt;
23+
_databaseQueue = databaseQueue;
2124

2225
return self;
2326
}
@@ -27,23 +30,27 @@ - (void)dealloc {
2730
}
2831

2932
- (BOOL)close {
30-
if (!_sqliteStatement) {
31-
return YES;
32-
}
33+
return PFThreadSafetyPerform(_databaseQueue, ^BOOL{
34+
if (!_sqliteStatement) {
35+
return YES;
36+
}
3337

34-
int resultCode = sqlite3_finalize(_sqliteStatement);
35-
_sqliteStatement = nil;
38+
int resultCode = sqlite3_finalize(_sqliteStatement);
39+
_sqliteStatement = nil;
3640

37-
return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
41+
return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
42+
});
3843
}
3944

4045
- (BOOL)reset {
41-
if (!_sqliteStatement) {
42-
return YES;
43-
}
44-
45-
int resultCode = sqlite3_reset(_sqliteStatement);
46-
return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
46+
return PFThreadSafetyPerform(_databaseQueue, ^BOOL{
47+
if (!_sqliteStatement) {
48+
return YES;
49+
}
50+
51+
int resultCode = sqlite3_reset(_sqliteStatement);
52+
return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
53+
});
4754
}
4855

4956
@end

Parse/Internal/ThreadSafety/PFThreadsafety.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@
1111

1212
extern dispatch_queue_t PFThreadsafetyCreateQueueForObject(id object);
1313
extern void PFThreadsafetySafeDispatchSync(dispatch_queue_t queue, dispatch_block_t block);
14+
15+
16+
// PFThreadsafetySafeDispatchSync, but with a return type.
17+
#define PFThreadSafetyPerform(queue, block) ({ \
18+
__block typeof((block())) result; \
19+
PFThreadsafetySafeDispatchSync(queue, ^{ result = block(); }); \
20+
result; \
21+
})

0 commit comments

Comments
 (0)