         /// - parameter settings: New settings.
         ///
         func setSettings(_ settings: JSONObject) throws {
-            assert(!finished)
-            settingsFilePath = try writeTmpJSONFile(json: settings)
+            try self_lock.sync {
+                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()
+            self_lock.sync {
+                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.
+        /// Commit the transaction.
         ///
         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
+            // Serialize calls with respect to this transaction.
+            try self_lock.sync {
+                assert(!finished)
+                try flushObjectsToDisk(force: true)
+                var error: Error?
+                // Also serialize build calls with respect to the client's build queue.
+                let buildOperation = BlockOperation() {
+                    let statusCode = Int(self.index.localIndex.build(settingsFile: self.settingsFilePath, objectFiles: self.objectFilePaths, clearIndex: self.shouldClearIndex, deletedObjectIDs: Array(self.deletedObjectIDs)))
+                    if statusCode != StatusCode.ok.rawValue {
+                        error = HTTPError(statusCode: statusCode)
+                    }
                 }
-            }
-            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(buildOperation)
+                buildOperation.waitUntilFinished()
+                finished = true
+                if error != nil {
+                    throw error!
                 }
             }
-            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
+            self_lock.sync {
+                _ = try? FileManager.default.removeItem(atPath: tmpDirPath)
+                finished = true
+            }
         }
         
         // MARK: Utils

        /// - parameter object: New version of the object to update. Must contain an `objectID` attribute.
        ///
        func saveObject(_ object: JSONObject) throws {
            assert(!finished)
            assert(object["objectID"] != nil, "Objects must contain an `objectID` attribute")
            tmpObjects.append(object)
            try flushObjectsToDisk(force: false)
        }
        
        /// Update several objects.

        /// - parameter objectID: Identifier of the object to delete.
        ///
        func deleteObject(withID objectID: String) throws {
            self_lock.sync {
                assert(!finished)
                deletedObjectIDs.insert(objectID)
            }
        }
        
        /// Delete several objects.
