Skip to content

Commit 6c65f16

Browse files
committed
ios: unblock readBlocking when the peripheral disconnects
The readBlocking loop would hang forever waiting for a semaphore, which is never signalled. We unblock it by signalling it upon disconnect. The current connection context now also has an identifier. This mitigates race conditions where e.g. a new connection is made before readBlocking resumes executing (to exit the loop).
1 parent 02fed0c commit 6c65f16

File tree

1 file changed

+29
-8
lines changed

1 file changed

+29
-8
lines changed

frontends/ios/BitBoxApp/BitBoxApp/Bluetooth.swift

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ var pairedDeviceIdentifiers: Set<String> {
5252
}
5353

5454
class BLEConnectionContext {
55+
let identifier = UUID()
5556
let semaphore = DispatchSemaphore(value: 0)
5657
var readBuffer = Data()
5758
var readBufferLock = NSLock()
@@ -294,9 +295,6 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
294295
}
295296

296297
func handleDisconnect() {
297-
currentContextLock.lock()
298-
currentContext = nil
299-
currentContextLock.unlock()
300298
connectedPeripheral = nil
301299
pReader = nil
302300
pWriter = nil
@@ -309,6 +307,12 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
309307
// Otherwise there would be up to a second of delay (the backend device manager scan interval).
310308
MobileserverUsbUpdate()
311309

310+
// Unblock a pending readBlocking() call if there is one.
311+
currentContextLock.lock()
312+
currentContext?.semaphore.signal()
313+
currentContext = nil
314+
currentContextLock.unlock()
315+
312316
restartScan()
313317
}
314318

@@ -318,25 +322,42 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
318322
handleDisconnect()
319323
}
320324

321-
func readBlocking(length: Int) -> Data? {
325+
struct ReadError: Error {
326+
let message: String
327+
}
328+
329+
func readBlocking(length: Int) throws -> Data {
322330
if !isConnected() {
323-
return nil
331+
throw ReadError(message: "not connected")
324332
}
325333
print("BLE: wants to read \(length)")
326334

327335
currentContextLock.lock()
328336
guard let ctx = currentContext else {
329337
currentContextLock.unlock()
330-
return nil
338+
throw ReadError(message: "no connection context")
331339
}
332340
currentContextLock.unlock()
341+
342+
let currentID = ctx.identifier;
333343

334344
var data = Data()
335345

336346
// Loop until we've read the required amount of data
337347
while data.count < length {
338-
// Block until BLE reader callback notifies us
348+
// Block until BLE reader callback notifies us or the peripheral is disconnected.
339349
ctx.semaphore.wait()
350+
351+
if !isConnected() {
352+
throw ReadError(message: "the peripheral has disconnected while reading")
353+
}
354+
currentContextLock.lock()
355+
let exit = currentContext?.identifier != currentID
356+
currentContextLock.unlock()
357+
if exit {
358+
throw ReadError(message: "the peripheral has disconnected while reading")
359+
}
360+
340361
ctx.readBufferLock.lock()
341362
data.append(ctx.readBuffer.prefix(64))
342363
ctx.readBuffer = ctx.readBuffer.advanced(by: 64)
@@ -485,7 +506,7 @@ class BluetoothReadWriteCloser: NSObject, MobileserverGoReadWriteCloserInterface
485506
}
486507

487508
func read(_ n: Int) throws -> Data {
488-
return bluetoothManager.readBlocking(length: n)!
509+
try bluetoothManager.readBlocking(length: n)
489510
}
490511

491512
func write(_ data: Data?, n: UnsafeMutablePointer<Int>?) throws {

0 commit comments

Comments
 (0)