         /// - parameter objects: New versions of the objects to update. Each one must contain an `objectID` attribute.
         ///
         func saveObjects(_ objects: [JSONObject]) throws {
-            assert(!finished)
-            tmpObjects.append(contentsOf: objects)
-            try flushObjectsToDisk(force: false)
+            try self_lock.sync {
+                assert(!finished)
+                tmpObjects.append(contentsOf: objects)
+                try flushObjectsToDisk(force: false)
+            }
         }
         
         /// Delete an object.

        /// - 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

        /// - parameter objectIDs: Identifiers of the objects to delete.
        ///
        func deleteObjects(withIDs objectIDs: [String]) throws {
            self_lock.sync {
                assert(!finished)
                deletedObjectIDs.formUnion(objectIDs)
            }
        }
        
        /// Set the index's settings.
