darwin: make Adapter.Connect thread-safe

This change allows multiple concurrent goroutines to call
`Adapter.Connect` without racing.

Fixes #57
This commit is contained in:
Erik Price 2021-03-16 16:49:19 -07:00 committed by Ron Evans
parent bb8767730c
commit cf63949412
2 changed files with 29 additions and 7 deletions

@ -2,6 +2,7 @@ package bluetooth
import ( import (
"errors" "errors"
"sync"
"time" "time"
"github.com/JuulLabs-OSS/cbgo" "github.com/JuulLabs-OSS/cbgo"
@ -18,7 +19,10 @@ type Adapter struct {
peripheralFoundHandler func(*Adapter, ScanResult) peripheralFoundHandler func(*Adapter, ScanResult)
scanChan chan error scanChan chan error
poweredChan 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) 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. // Make sure to call Enable() before using it to initialize the adapter.
var DefaultAdapter = &Adapter{ var DefaultAdapter = &Adapter{
cm: cbgo.NewCentralManager(nil), cm: cbgo.NewCentralManager(nil),
pm: cbgo.NewPeripheralManager(nil), pm: cbgo.NewPeripheralManager(nil),
connectChan: make(chan cbgo.Peripheral), connectMap: sync.Map{},
connectHandler: func(device Addresser, connected bool) { connectHandler: func(device Addresser, connected bool) {
return return
}, },
@ -97,8 +102,18 @@ func (cmd *centralManagerDelegate) DidDiscoverPeripheral(cmgr cbgo.CentralManage
// DidConnectPeripheral when peripheral is connected. // DidConnectPeripheral when peripheral is connected.
func (cmd *centralManagerDelegate) DidConnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral) { func (cmd *centralManagerDelegate) DidConnectPeripheral(cmgr cbgo.CentralManager, prph cbgo.Peripheral) {
// Unblock now that we're connected. id := prph.Identifier().String()
cmd.a.connectChan <- prph
// 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. // makeScanResult creates a ScanResult when peripheral is found.

@ -105,11 +105,18 @@ func (a *Adapter) Connect(address Addresser, params ConnectionParams) (*Device,
if len(prphs) == 0 { if len(prphs) == 0 {
return nil, fmt.Errorf("Connect failed: no peer with address: %s", adr.UUID.String()) 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) a.cm.Connect(prphs[0], nil)
// wait on channel for connect // wait on channel for connect
select { select {
case p := <-a.connectChan: case p := <-prphCh:
d := &Device{ d := &Device{
cm: a.cm, cm: a.cm,
prph: p, prph: p,