Concurrency Control Mechanism in BitChat's BLE Media Transfers: How It Works
BitChat serializes all BLE media transfer operations through a centralized NSLock-based coordination layer in BLEIncomingFileStore, combining atomic file writes, serial dispatch queues, and durable panic-recovery markers to eliminate race conditions during concurrent file operations.
BitChat, an open-source peer-to-peer messaging application by permissionlesstech, handles media transfers over Bluetooth Low Energy (BLE) with strict concurrency guarantees. The concurrency control mechanism in BitChat's BLE media transfers relies on a lightweight but robust locking strategy implemented in the BLEIncomingFileStore class. This deterministic design ensures that simultaneous inbound and outbound file operations never corrupt the payload store, even under heavy load or process interruption.
Core Concurrency Components
BitChat's BLE media layer employs four complementary mechanisms to coordinate access to shared file system resources.
PayloadCoordination and NSLock
The primary synchronization primitive is PayloadCoordination, a small coordinator class that encapsulates an NSLock and two critical mutable sets: pendingDeliveryPaths (files that have just been written) and deletionReservations (paths reserved for pending deletion). According to the source code in [BLEIncomingFileStore.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEIncomingFileStore.swift#L110-L118), all mutations to these sets are wrapped in payloadCoordination.lock.lock() and unlock() calls. This guarantees that only one thread can add, remove, or query the delivery and deletion states at any given moment.
Atomic File Writes
When persisting payload data, BitChat uses Data.WritingOptions.atomic (and on iOS, .completeFileProtectionUntilFirstUserAuthentication). As implemented in the save() method, the atomic flag forces the system to write to a temporary file and then rename it to the final destination. This prevents other threads from observing partially-written files during concurrent access.
Serial BLE Dispatch Queue
The BLE stack itself runs on a dedicated serial DispatchQueue named messageQueue, identified by a queue-specific key. As shown in [BLEService.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift#L376-L383), all BLE message handling—including the hand-off of media payloads—asserts execution on this queue. This ensures that no two Bluetooth events race each other before reaching the file store.
File-System Markers for Crash Recovery
BitChat implements a two-phase commit mechanism using panic-recovery markers (.panic-recovery-pending). Before any media wipe operation, the markPanicRecoveryPending() method writes a durable marker that survives process crashes. The marker is deleted only after the wipe succeeds, preventing simultaneous recovery operations from corrupting application state.
Implementation in Code
The following examples demonstrate how these mechanisms work together in the permissionlesstech/bitchat codebase.
Serializing Media Writes with NSLock
Every public entry point in BLEIncomingFileStore grabs the coordination lock before touching mutable state or the file system. The save() method exemplifies this pattern:
func save(
data: Data,
preferredName: String?,
subdirectory: String,
fallbackExtension: String?,
defaultPrefix: String
) -> URL? {
payloadCoordination.lock.lock() // ← exclusive access begins
defer { payloadCoordination.lock.unlock() }
do {
let base = try filesDirectory().appendingPathComponent(subdirectory, isDirectory: true)
try fileManager.createDirectory(at: base, withIntermediateDirectories: true,
attributes: Self.mediaProtectionAttributes)
let sanitized = sanitizedFileName(preferredName,
defaultName: "\(defaultPrefix)_\(Self.timestampString(from: dateProvider()))",
fallbackExtension: fallbackExtension)
let destination = uniqueFileURL(in: base,
fileName: sanitized,
reservedPaths: /* … */,
forceRandomizedName: false)
var options: Data.WritingOptions = [.atomic] // atomic write
#if os(iOS)
options.insert(.completeFileProtectionUntilFirstUserAuthentication)
#endif
try data.write(to: destination, options: options) // ← atomic commit
payloadCoordination.pendingDeliveryPaths.insert(destination.standardizedFileURL.path)
return destination
} catch {
SecureLogger.error("❌ Failed to persist incoming media: \(error)", category: .session)
return nil
}
}
The lock guarantees that pendingDeliveryPaths cannot be mutated concurrently by another thread, while the atomic write ensures the file appears on disk only after the entire payload is committed.
Coordinating Deletion Reservations
When reserving media for deletion, the reservePrivateMediaDeletion() function checks for conflicts under the same lock:
func reservePrivateMediaDeletion(messageIDs: [String],
payloadRelativePaths: [String: String])
-> PrivateMediaDeletionReservation? {
payloadCoordination.lock.lock()
defer { payloadCoordination.lock.unlock() }
guard let paths = privateMediaReceipts
.prospectiveDeletionPayloadPaths(messageIDs: messageIDs,
payloadRelativePaths: payloadRelativePaths),
paths.isDisjoint(with: payloadCoordination.pendingDeliveryPaths) else {
return nil // another thread already owns these paths
}
let reservation = PrivateMediaDeletionReservation(id: UUID())
payloadCoordination.deletionReservations[reservation.id] = paths
return reservation
}
Only one thread can create a reservation for a specific set of paths; concurrent attempts see the conflict and return nil, preventing double-deletion or deletion of actively transferring files.
Enforcing Queue Serialization
Within [BLEService.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift), the serial queue ensures ordered processing of all Bluetooth events:
// Inside BLEService.swift
private let messageQueue = DispatchQueue(label: "mesh.message")
private let messageQueueKey = DispatchSpecificKey<Void>()
messageQueue.setSpecific(key: messageQueueKey, value: ())
func handleIncoming(_ data: Data) {
// Ensure we are on the serial BLE queue
assert(DispatchQueue.getSpecific(key: messageQueueKey) != nil,
"BLE handling must run on messageQueue")
// …process data, possibly calling BLEIncomingFileStore.save(...)
}
All BLE-related work is forced onto messageQueue, so even if multiple Bluetooth events arrive simultaneously, they are processed sequentially before invoking the file store.
Summary
- Centralized Locking: A single
NSLockinsidePayloadCoordinationprotects all mutable state inBLEIncomingFileStore, ensuring thread-safe access topendingDeliveryPathsanddeletionReservations. - Atomic Persistence: All file writes use
Data.WritingOptions.atomic, which writes to temporary storage and renames on completion, preventing corruption from partial writes. - Serial Queue: The dedicated
messageQueueinBLEService.swiftserializes all BLE event handling before they reach the media store. - Crash Safety: Panic-recovery markers provide a durable two-phase commit mechanism that survives process crashes, ensuring media wipes are atomic and recoverable.
Frequently Asked Questions
What type of lock does BitChat use for BLE media concurrency control?
BitChat uses an NSLock encapsulated within the PayloadCoordination class located in BLEIncomingFileStore.swift. This is a traditional mutex lock that blocks concurrent threads from entering critical sections that manipulate the pendingDeliveryPaths and deletionReservations sets or perform file system operations.
How does BitChat prevent other threads from reading incomplete media files?
BitChat employs atomic file writes using Data.WritingOptions.atomic (and .completeFileProtectionUntilFirstUserAuthentication on iOS). This option forces the operating system to write data to a temporary file and perform an atomic rename operation only after the write completes successfully. Consequently, other threads cannot observe partially-written files because the destination path does not exist until the entire payload is committed to disk.
Why does BitChat use a serial dispatch queue for BLE operations?
The serial DispatchQueue named messageQueue (defined in BLEService.swift) ensures that all Bluetooth-related callbacks and message processing happen sequentially rather than concurrently. This eliminates race conditions at the transport layer and guarantees that media transfer hand-offs to BLEIncomingFileStore occur in a predictable order, simplifying the concurrency model above the file system layer.
What happens if BitChat crashes during a media deletion operation?
BitChat implements panic-recovery markers (.panic-recovery-pending files) that act as durable locks surviving process crashes. Before wiping media, the markPanicRecoveryPending() method creates a marker; after successful deletion, the marker is removed. If a crash occurs, the presence of the marker on restart triggers recovery logic that prevents simultaneous or duplicate wipe operations from corrupting the media store state.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →