From 3fdec832f7116f063b955bfa1cfbb20c19dba73e Mon Sep 17 00:00:00 2001 From: Adam Sharp Date: Sat, 21 Apr 2018 14:11:49 -0400 Subject: [PATCH] Add an optional target queue to for database operations The optional target queue is useful for synchronising other work with respect to database operations. Provided the target queue is also a serial queue, work submitted to the private connection queue and work submitted directly to the target queue will still be executed in serial. The queue context is now set on the target queue if present. The connection queue inherits values set on the target queue, so the call to `DispatchQueue.getSpecific`, using `queueKey`, returns `queueContext` as expected. For this logic to still be thread-safe, the target queue must be a serial queue. `queueKey` is now an instance variable so that every connection gets a unique key, ensuring that connections can't write over one another's context variables. The `attributes:` parameter is also removed from the call to initialise the database queue, as `[]` (i.e., serial) is already the default. --- Sources/SQLite/Core/Connection.swift | 31 +++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1bbf7f73..df433c27 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -101,11 +101,21 @@ public final class Connection { /// /// Default: `false`. /// + /// - target: An optional target queue for database operations to be executed on. + /// Set this to a custom serial queue if you need to schedule work that must be + /// synchronized with respect to database operations. + /// + /// **Important:** The target queue **must not** be a concurrent queue, or access + /// to the returned connection is no longer guaranteed be thread-safe. + /// + /// - Throws: `Result.Error` iff a connection cannot be established. + /// /// - Returns: A new database connection. - public init(_ location: Location = .inMemory, readonly: Bool = false) throws { + public init(_ location: Location = .inMemory, readonly: Bool = false, target: DispatchQueue? = nil) throws { + queue = DispatchQueue(label: "SQLite.Database", target: target) let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) - queue.setSpecific(key: Connection.queueKey, value: queueContext) + (target ?? queue).setSpecific(key: queueKey, value: queueContext) } /// Initializes a new connection to a database. @@ -119,11 +129,18 @@ public final class Connection { /// /// Default: `false`. /// + /// - target: An optional target queue for database operations to be executed on. + /// Set this to a custom serial queue if you need to schedule work that must be + /// synchronized with respect to database operations. + /// + /// **Important:** The target queue **must not** be a concurrent queue, or access + /// to the returned connection is no longer guaranteed be thread-safe. + /// /// - Throws: `Result.Error` iff a connection cannot be established. /// /// - Returns: A new database connection. - public convenience init(_ filename: String, readonly: Bool = false) throws { - try self.init(.uri(filename), readonly: readonly) + public convenience init(_ filename: String, readonly: Bool = false, target: DispatchQueue? = nil) throws { + try self.init(.uri(filename), readonly: readonly, target: target) } deinit { @@ -631,7 +648,7 @@ public final class Connection { // MARK: - Error Handling func sync(_ block: () throws -> T) rethrows -> T { - if DispatchQueue.getSpecific(key: Connection.queueKey) == queueContext { + if DispatchQueue.getSpecific(key: queueKey) == queueContext { return try block() } else { return try queue.sync(execute: block) @@ -646,9 +663,9 @@ public final class Connection { throw error } - fileprivate var queue = DispatchQueue(label: "SQLite.Database", attributes: []) + fileprivate var queue: DispatchQueue - fileprivate static let queueKey = DispatchSpecificKey() + fileprivate let queueKey = DispatchSpecificKey() fileprivate lazy var queueContext: Int = unsafeBitCast(self, to: Int.self)