From cf6394941262e81e37c77e6dc9fe0841ba0eb81e Mon Sep 17 00:00:00 2001 From: Erik Price Date: Tue, 16 Mar 2021 16:49:19 -0700 Subject: [PATCH] darwin: make Adapter.Connect thread-safe This change allows multiple concurrent goroutines to call `Adapter.Connect` without racing. Fixes #57 --- adapter_darwin.go | 27 +++++++++++++++++++++------ gap_darwin.go | 9 ++++++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/adapter_darwin.go b/adapter_darwin.go index d721fe2..2123ab3 100644 --- a/adapter_darwin.go +++ b/adapter_darwin.go @@ -2,6 +2,7 @@ package bluetooth import ( "errors" + "sync" "time" "github.com/JuulLabs-OSS/cbgo" @@ -18,7 +19,10 @@ type Adapter struct { peripheralFoundHandler func(*Adapter, ScanResult) scanChan chan error poweredChan chan error - connectChan chan cbgo.Peripheral + + // connectMap is a mapping of peripheralId -> chan cbgo.Peripheral, + // used to allow multiple callers to call Connect concurrently. + connectMap sync.Map connectHandler func(device Addresser, connected bool) } @@ -27,9 +31,10 @@ type Adapter struct { // // Make sure to call Enable() before using it to initialize the adapter. var DefaultAdapter = &Adapter{ - cm: cbgo.NewCentralManager(nil), - pm: cbgo.NewPeripheralManager(nil), - connectChan: make(chan cbgo.Peripheral), + cm: cbgo.NewCentralManager(nil), + pm: cbgo.NewPeripheralManager(nil), + connectMap: sync.Map{}, + connectHandler: func(device Addresser, connected bool) { return }, @@ -97,8 +102,18 @@ func (cmd *centralManagerDelegate) DidDiscoverPeripheral(cmgr cbgo.CentralManage // DidConnectPeripheral when peripheral is connected. func (cmd *centralManagerDelegate) DidConnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral) { - // Unblock now that we're connected. - cmd.a.connectChan <- prph + id := prph.Identifier().String() + + // Check if we have a chan allocated for this peripheral, and remove it + // from the map if so (it's single-use, will be garbage collected after + // receiver receives the peripheral). + // + // If we don't have a chan allocated, the receiving side timed out, so + // ignore this connection. + if ch, ok := cmd.a.connectMap.LoadAndDelete(id); ok { + // Unblock now that we're connected. + ch.(chan cbgo.Peripheral) <- prph + } } // makeScanResult creates a ScanResult when peripheral is found. diff --git a/gap_darwin.go b/gap_darwin.go index 19b4af8..644d5c7 100644 --- a/gap_darwin.go +++ b/gap_darwin.go @@ -105,11 +105,18 @@ func (a *Adapter) Connect(address Addresser, params ConnectionParams) (*Device, if len(prphs) == 0 { return nil, fmt.Errorf("Connect failed: no peer with address: %s", adr.UUID.String()) } + + id := prphs[0].Identifier().String() + prphCh := make(chan cbgo.Peripheral) + + a.connectMap.Store(id, prphCh) + defer a.connectMap.Delete(id) + a.cm.Connect(prphs[0], nil) // wait on channel for connect select { - case p := <-a.connectChan: + case p := <-prphCh: d := &Device{ cm: a.cm, prph: p,