         /// Temporary in-memory cache for objects.
         private var tmpObjects: [JSONObject] = []
         
+        /// Dispatch queue used to serialize accesses to a transaction's data.
+        private var self_lock = DispatchQueue(label: String(reflecting: type(of: self)) + ".lock")
+        
         // MARK: Constants
         
         /// Maximum number of objects to keep in memory before flushing to disk.

        /// - parameter settings: New settings.
        ///
        func setSettings(_ settings: JSONObject) throws {
            assert(!finished)
            settingsFilePath = try writeTmpJSONFile(json: settings)
        }
        
        /// Delete the index content without removing settings.
        ///
        func clearIndex() throws {
            assert(!finished)
            shouldClearIndex = true
            deletedObjectIDs.removeAll()
            objectFilePaths.removeAll()
        }
        
        // MARK: Completion
        
        /// Commit the transaction (synchronously).
        ///
        /// + Warning: This method blocks until completion. Must not be called from the main thread.
        ///
        func commit() throws {
            assert(!Thread.isMainThread)
            // TODO: Would be easier with dispatch queue. See if we can use underlyingQueue (drop iOS 7 support).
            var error: Error?
            let operation = BlockOperation() {
                do {
                    try self._commit()
                } catch let e {
                    error = e
                }
            }
            index.client.buildQueue.addOperation(operation)
            operation.waitUntilFinished()
            if error != nil {
                throw error!
            }
        }
        
        /// Commit the transaction (asynchronously).
        func commit(completionHandler: CompletionHandler?) {
            // TODO: Not sure it's needed.
            let operation = BlockOperation() {
                do {
                    try self._commit()
                    completionHandler?([:], nil)
                } catch let e {
                    completionHandler?(nil, e)
                }
            }
            index.client.buildQueue.addOperation(operation)
        }
        
        private func _commit() throws {
            assert(!finished)
            try flushObjectsToDisk(force: true)
            let statusCode = Int(index.localIndex.build(settingsFile: settingsFilePath, objectFiles: objectFilePaths, clearIndex: shouldClearIndex, deletedObjectIDs: Array(deletedObjectIDs)))
            if statusCode != StatusCode.ok.rawValue {
                throw HTTPError(statusCode: statusCode)
            }
            finished = true
        }
        
        /// Rollback the transaction.
        /// The index will be left untouched.
        ///
        func rollback() {
            _ = try? FileManager.default.removeItem(atPath: tmpDirPath)
            finished = true
        }
        
        // MARK: Utils

            transaction = WriteTransaction(index: self)
        }
    }

    @discardableResult
    public func commitTransaction(completionHandler: @escaping CompletionHandler) -> Operation {
        assert(transaction != nil, "No transaction is open")
        let operation = BlockOperation() {
            let transaction = self.transaction!
            self.transaction = nil
            do {
                try transaction.commit()
                self.callCompletionHandler(completionHandler, content: [:], error: nil)
            } catch let e {
                self.callCompletionHandler(completionHandler, content: nil, error: e)
            }
        }
        transactionQueue.addOperation(operation)
        return operation
    }
    
    public func commitTransactionSync() throws {
        assert(!Thread.isMainThread, "Synchronous methods should not be called from the main thread")
        try transaction!.commit()
    }
    
    public func rollbackTransaction() {
