@@ -51,6 +51,12 @@ var pairedDeviceIdentifiers: Set<String> {
51
51
}
52
52
}
53
53
54
+ class BLEConnectionContext {
55
+ let semaphore = DispatchSemaphore ( value: 0 )
56
+ var readBuffer = Data ( )
57
+ var readBufferLock = NSLock ( )
58
+ }
59
+
54
60
class BluetoothManager : NSObject , ObservableObject , CBCentralManagerDelegate , CBPeripheralDelegate {
55
61
private var state : State = State (
56
62
bluetoothAvailable: false ,
@@ -70,9 +76,11 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
70
76
// This is for failed connections to not enter an infinite connect loop.
71
77
private var dontAutoConnectSet : Set < UUID > = [ ]
72
78
73
- private var readBuffer = Data ( )
74
- private let readBufferLock = NSLock ( ) // Ensure thread-safe buffer access
75
- private let semaphore = DispatchSemaphore ( value: 0 )
79
+ private var currentContext : BLEConnectionContext ?
80
+ // Locks access to the `currentContext` var only, not to its contents. This is important, as
81
+ // one can't keep the context locked while waiting for the semaphore, which would lead to a
82
+ // deadlock.
83
+ private let currentContextLock = NSLock ( )
76
84
77
85
override init ( ) {
78
86
super. init ( )
@@ -93,6 +101,9 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
93
101
state. discoveredPeripherals [ peripheralID] = metadata
94
102
state. scanning = false
95
103
updateBackendState ( )
104
+ currentContextLock. lock ( )
105
+ currentContext = BLEConnectionContext ( )
106
+ currentContextLock. unlock ( )
96
107
centralManager. connect ( metadata. peripheral, options: nil )
97
108
}
98
109
@@ -246,17 +257,24 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
246
257
return
247
258
}
248
259
260
+ currentContextLock. lock ( )
261
+ guard let ctx = currentContext else {
262
+ currentContextLock. unlock ( )
263
+ return
264
+ }
265
+ currentContextLock. unlock ( )
266
+
249
267
if characteristic == pReader, let data = characteristic. value {
250
268
if data. count != 64 {
251
269
print ( " BLE: ERROR, expected 64 bytes " )
252
270
}
253
271
print ( " BLE: received data: \( data. hexEncodedString ( ) ) " )
254
- readBufferLock. lock ( )
255
- readBuffer. append ( data)
256
- readBufferLock. unlock ( )
272
+ ctx . readBufferLock. lock ( )
273
+ ctx . readBuffer. append ( data)
274
+ ctx . readBufferLock. unlock ( )
257
275
258
276
// Signal the semaphore to unblock `readBlocking`
259
- semaphore. signal ( )
277
+ ctx . semaphore. signal ( )
260
278
}
261
279
if characteristic == pProduct {
262
280
print ( " BLE: product changed: \( String ( describing: parseProduct ( ) ) ) " )
@@ -276,12 +294,15 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
276
294
}
277
295
278
296
func handleDisconnect( ) {
297
+ currentContextLock. lock ( )
298
+ currentContext = nil
299
+ currentContextLock. unlock ( )
279
300
connectedPeripheral = nil
280
301
pReader = nil
281
302
pWriter = nil
282
303
pProduct = nil
283
304
state. discoveredPeripherals. removeAll ( )
284
- isPaired = false
305
+ isPaired = false
285
306
updateBackendState ( )
286
307
287
308
// Have the backend scan right away, which will make it detect that we disconnected.
@@ -303,16 +324,23 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
303
324
}
304
325
print ( " BLE: wants to read \( length) " )
305
326
327
+ currentContextLock. lock ( )
328
+ guard let ctx = currentContext else {
329
+ currentContextLock. unlock ( )
330
+ return nil
331
+ }
332
+ currentContextLock. unlock ( )
333
+
306
334
var data = Data ( )
307
335
308
336
// Loop until we've read the required amount of data
309
337
while data. count < length {
310
338
// Block until BLE reader callback notifies us
311
- semaphore. wait ( )
312
- readBufferLock. lock ( )
313
- data. append ( readBuffer. prefix ( 64 ) )
314
- readBuffer = readBuffer. advanced ( by: 64 )
315
- readBufferLock. unlock ( )
339
+ ctx . semaphore. wait ( )
340
+ ctx . readBufferLock. lock ( )
341
+ data. append ( ctx . readBuffer. prefix ( 64 ) )
342
+ ctx . readBuffer = ctx . readBuffer. advanced ( by: 64 )
343
+ ctx . readBufferLock. unlock ( )
316
344
}
317
345
print ( " BLE: got \( data. count) " )
318
346
0 commit comments