In src/winrtble/peripheral.rs (write, read, subscribe, unsubscribe, read_descriptor, write_descriptor), the service lookup is written as:
let ble_service = &*self.shared.ble_services.get(&characteristic.service_uuid).ok_or_else(...)?;
Temporary lifetime extension keeps the DashMap Ref/RefMut guard alive until the end of the function, so the shard lock is held across ble_characteristic.write_value(..).await / .subscribe(..).await etc. DashMap locks are blocking, not async-aware.
Failure scenario: futures::join!(p.subscribe(&a), p.subscribe(&b)) with a, b in the same service: the first future holds the write lock while awaiting the CCCD write; the second blocks the thread in get_mut, so the first can never be polled again. Permanent deadlock. Also reachable on a current-thread runtime with any concurrent op + subscribe, or disconnect() (ble_services.clear()) racing an in-flight op.
Fix direction: clone what's needed (the characteristic handle) out of the map and drop the guard before awaiting.
In
src/winrtble/peripheral.rs(write,read,subscribe,unsubscribe,read_descriptor,write_descriptor), the service lookup is written as:Temporary lifetime extension keeps the DashMap
Ref/RefMutguard alive until the end of the function, so the shard lock is held acrossble_characteristic.write_value(..).await/.subscribe(..).awaitetc. DashMap locks are blocking, not async-aware.Failure scenario:
futures::join!(p.subscribe(&a), p.subscribe(&b))witha,bin the same service: the first future holds the write lock while awaiting the CCCD write; the second blocks the thread inget_mut, so the first can never be polled again. Permanent deadlock. Also reachable on a current-thread runtime with any concurrent op +subscribe, ordisconnect()(ble_services.clear()) racing an in-flight op.Fix direction: clone what's needed (the characteristic handle) out of the map and drop the guard before awaiting.