Skip to content

Made all accesses to PFSQLiteDatabaseResult & Statement thread-safe. #511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions Parse/Internal/LocalDataStore/SQLite/PFSQLiteDatabase.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#import "PFSQLiteDatabaseResult.h"
#import "PFSQLiteStatement.h"
#import "Parse_Private.h"
#import "PFThreadsafety.h"

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

_databaseClosedTaskCompletionSource = [[BFTaskCompletionSource alloc] init];
_databasePath = [path copy];
_databaseQueue = dispatch_queue_create("com.parse.sqlite.db.queue", DISPATCH_QUEUE_SERIAL);
_databaseExecutor = [BFExecutor executorWithDispatchQueue:_databaseQueue];

_databaseQueue = PFThreadsafetyCreateQueueForObject(self);
_databaseExecutor = [BFExecutor executorWithBlock:^(dispatch_block_t block) {
// Execute asynchrounously on the proper queue.
// Seems a bit backwards, but we don't have PFThreadsafetySafeDispatchAsync.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
PFThreadsafetySafeDispatchSync(_databaseQueue, block);
});
}];

_cachedStatements = [[NSMutableDictionary alloc] init];

return self;
Expand Down Expand Up @@ -178,7 +187,7 @@ - (BFTask *)_executeQueryAsync:(NSString *)sql withArgumentsInArray:(NSArray *)a
sqlite3_finalize(sqliteStatement);
return [BFTask taskWithError:[self _errorWithErrorCode:resultCode]];
}
statement = [[PFSQLiteStatement alloc] initWithStatement:sqliteStatement];
statement = [[PFSQLiteStatement alloc] initWithStatement:sqliteStatement queue:_databaseQueue];

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

PFSQLiteDatabaseResult *result = [[PFSQLiteDatabaseResult alloc] initWithStatement:statement];
PFSQLiteDatabaseResult *result = [[PFSQLiteDatabaseResult alloc] initWithStatement:statement queue:_databaseQueue];
return [BFTask taskWithResult:result];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface PFSQLiteDatabaseResult : NSObject

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

/*!
Move current result to next row. Returns true if next result exists. False if current result
Expand Down
107 changes: 65 additions & 42 deletions Parse/Internal/LocalDataStore/SQLite/PFSQLiteDatabaseResult.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@
#import <sqlite3.h>

#import "PFSQLiteStatement.h"
#import "PFThreadsafety.h"

@interface PFSQLiteDatabaseResult ()

@property (nonatomic, copy, readonly) NSDictionary *columnNameToIndexMap;
@property (nonatomic, strong, readonly) PFSQLiteStatement *statement;
@property (nonatomic, strong, readonly) dispatch_queue_t databaseQueue;

@end

@implementation PFSQLiteDatabaseResult

@synthesize columnNameToIndexMap = _columnNameToIndexMap;

- (instancetype)initWithStatement:(PFSQLiteStatement *)stmt {
- (instancetype)initWithStatement:(PFSQLiteStatement *)stmt queue:(dispatch_queue_t)queue {
if ((self = [super init])) {
_statement = stmt;
_databaseQueue = queue;
}
return self;
}
Expand All @@ -36,7 +39,9 @@ - (BOOL)next {
}

- (int)step {
return sqlite3_step([self.statement sqliteStatement]);
return PFThreadSafetyPerform(_databaseQueue, ^{
return sqlite3_step([self.statement sqliteStatement]);
});
}

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

- (int)intForColumnIndex:(int)columnIndex {
return sqlite3_column_int([self.statement sqliteStatement], columnIndex);
return PFThreadSafetyPerform(_databaseQueue, ^{
return sqlite3_column_int([self.statement sqliteStatement], columnIndex);
});
}

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

- (long)longForColumnIndex:(int)columnIndex {
return (long)sqlite3_column_int64([self.statement sqliteStatement], columnIndex);
return PFThreadSafetyPerform(_databaseQueue, ^{
return (long)sqlite3_column_int64([self.statement sqliteStatement], columnIndex);
});
}

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

- (BOOL)boolForColumnIndex:(int)columnIndex {
return ([self intForColumnIndex:columnIndex] != 0);
return PFThreadSafetyPerform(_databaseQueue, ^{
return ([self intForColumnIndex:columnIndex] != 0);
});
}

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

- (double)doubleForColumnIndex:(int)columnIndex {
return sqlite3_column_double([self.statement sqliteStatement], columnIndex);
return PFThreadSafetyPerform(_databaseQueue, ^{
return sqlite3_column_double([self.statement sqliteStatement], columnIndex);
});
}

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

- (NSString *)stringForColumnIndex:(int)columnIndex {
if ([self columnIndexIsNull:columnIndex]) {
return nil;
}
return PFThreadSafetyPerform(_databaseQueue, ^NSString *{
if ([self columnIndexIsNull:columnIndex]) {
return nil;
}

const char *str = (const char *)sqlite3_column_text([self.statement sqliteStatement], columnIndex);
if (!str) {
return nil;
}
return [NSString stringWithUTF8String:str];
const char *str = (const char *)sqlite3_column_text([self.statement sqliteStatement], columnIndex);
if (!str) {
return nil;
}
return [NSString stringWithUTF8String:str];
});
}

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

- (NSData *)dataForColumnIndex:(int)columnIndex {
if ([self columnIndexIsNull:columnIndex]) {
return nil;
}
return PFThreadSafetyPerform(_databaseQueue, ^NSData *{
if ([self columnIndexIsNull:columnIndex]) {
return nil;
}

int size = sqlite3_column_bytes([self.statement sqliteStatement], columnIndex);
const char *buffer = sqlite3_column_blob([self.statement sqliteStatement], columnIndex);
if (buffer == nil) {
return nil;
}
return [NSData dataWithBytes:buffer length:size];
int size = sqlite3_column_bytes([self.statement sqliteStatement], columnIndex);
const char *buffer = sqlite3_column_blob([self.statement sqliteStatement], columnIndex);
if (buffer == nil) {
return nil;
}
return [NSData dataWithBytes:buffer length:size];
});
}

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

- (id)objectForColumnIndex:(int)columnIndex {
int columnType = sqlite3_column_type([self.statement sqliteStatement], columnIndex);
switch (columnType) {
case SQLITE_INTEGER:
return @([self longForColumnIndex:columnIndex]);
case SQLITE_FLOAT:
return @([self doubleForColumnIndex:columnIndex]);
case SQLITE_BLOB:
return [self dataForColumnIndex:columnIndex];
default:
return [self stringForColumnIndex:columnIndex];
}
return PFThreadSafetyPerform(_databaseQueue, ^id{
int columnType = sqlite3_column_type([self.statement sqliteStatement], columnIndex);
switch (columnType) {
case SQLITE_INTEGER:
return @([self longForColumnIndex:columnIndex]);
case SQLITE_FLOAT:
return @([self doubleForColumnIndex:columnIndex]);
case SQLITE_BLOB:
return [self dataForColumnIndex:columnIndex];
default:
return [self stringForColumnIndex:columnIndex];
}
});
}

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

- (BOOL)columnIndexIsNull:(int)columnIndex {
return (sqlite3_column_type([self.statement sqliteStatement], columnIndex) == SQLITE_NULL);
return PFThreadSafetyPerform(_databaseQueue, ^{
return (sqlite3_column_type([self.statement sqliteStatement], columnIndex) == SQLITE_NULL);
});
}

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

- (NSDictionary *)columnNameToIndexMap {
if (!_columnNameToIndexMap) {
int columnCount = sqlite3_column_count([self.statement sqliteStatement]);
NSMutableDictionary *mutableColumnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:columnCount];
for (int i = 0; i < columnCount; ++i) {
NSString *key = [NSString stringWithUTF8String:sqlite3_column_name([self.statement sqliteStatement], i)];
mutableColumnNameToIndexMap[[key lowercaseString]] = @(i);
}
_columnNameToIndexMap = mutableColumnNameToIndexMap;
PFThreadsafetySafeDispatchSync(_databaseQueue, ^{
int columnCount = sqlite3_column_count([self.statement sqliteStatement]);
NSMutableDictionary *mutableColumnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:columnCount];
for (int i = 0; i < columnCount; ++i) {
NSString *key = [NSString stringWithUTF8String:sqlite3_column_name([self.statement sqliteStatement], i)];
mutableColumnNameToIndexMap[[key lowercaseString]] = @(i);
}
_columnNameToIndexMap = mutableColumnNameToIndexMap;
});
}
return _columnNameToIndexMap;
}
Expand Down
5 changes: 3 additions & 2 deletions Parse/Internal/LocalDataStore/SQLite/PFSQLiteStatement.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ typedef struct sqlite3_stmt sqlite3_stmt;

@interface PFSQLiteStatement : NSObject

@property (atomic, assign, readonly) sqlite3_stmt *sqliteStatement;
@property (nonatomic, assign, readonly) sqlite3_stmt *sqliteStatement;
@property (nonatomic, strong, readonly) dispatch_queue_t databaseQueue;

- (instancetype)initWithStatement:(sqlite3_stmt *)stmt;
- (instancetype)initWithStatement:(sqlite3_stmt *)stmt queue:(dispatch_queue_t)databaseQueue;

- (BOOL)close;
- (BOOL)reset;
Expand Down
33 changes: 20 additions & 13 deletions Parse/Internal/LocalDataStore/SQLite/PFSQLiteStatement.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@

#import <sqlite3.h>

#import "PFThreadsafety.h"

@implementation PFSQLiteStatement

- (instancetype)initWithStatement:(sqlite3_stmt *)stmt {
- (instancetype)initWithStatement:(sqlite3_stmt *)stmt queue:(dispatch_queue_t)databaseQueue {
self = [super init];
if (!stmt || !self) return nil;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really need to clean this up at some point... if (!stmt) sounds pretty damn bad...


_sqliteStatement = stmt;
_databaseQueue = databaseQueue;

return self;
}
Expand All @@ -27,23 +30,27 @@ - (void)dealloc {
}

- (BOOL)close {
if (!_sqliteStatement) {
return YES;
}
return PFThreadSafetyPerform(_databaseQueue, ^BOOL{
if (!_sqliteStatement) {
return YES;
}

int resultCode = sqlite3_finalize(_sqliteStatement);
_sqliteStatement = nil;
int resultCode = sqlite3_finalize(_sqliteStatement);
_sqliteStatement = nil;

return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
});
}

- (BOOL)reset {
if (!_sqliteStatement) {
return YES;
}

int resultCode = sqlite3_reset(_sqliteStatement);
return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
return PFThreadSafetyPerform(_databaseQueue, ^BOOL{
if (!_sqliteStatement) {
return YES;
}

int resultCode = sqlite3_reset(_sqliteStatement);
return (resultCode == SQLITE_OK || resultCode == SQLITE_DONE);
});
}

@end
8 changes: 8 additions & 0 deletions Parse/Internal/ThreadSafety/PFThreadsafety.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@

extern dispatch_queue_t PFThreadsafetyCreateQueueForObject(id object);
extern void PFThreadsafetySafeDispatchSync(dispatch_queue_t queue, dispatch_block_t block);


// PFThreadsafetySafeDispatchSync, but with a return type.
#define PFThreadSafetyPerform(queue, block) ({ \
__block typeof((block())) result; \
PFThreadsafetySafeDispatchSync(queue, ^{ result = block(); }); \
result; \
})