Skip to content

Commit 9c334ed

Browse files
cmaglieumbynos
andauthored
Fix gRPC BoardList* methods concurrency issues (#1804)
* Improved streaming of pluggable-discoveries events (WIP) Now the DiscoveryManager is able to start the discoveries and add/remove them in a thread-safe way. Also the watchers may connect and disconnect seamlessly at any time, the incoming events from the discovery are broadcasted correctly to each active watcher. This refactoring dramatically simplifies the DiscoveryManager design. * Added discovery id in discovery.Event struct * Cache active ports and transmit them when a new watcher connects * Correctly handle discovery cleanup * Fixed wrong test * Correctly handle discovery cleanup and re-add * Added some doc comments in the source code * Move Unlock under defer * Factored subrotuine into a function it will be useful in the next commits. * Do not cache ports in the DiscoveryClient there is already a cache in the DiscoveryManager there is no need to duplicate it. * Discovery: eventChan must be protected by mutex when doing START_SYNC otherwise the discovery may send some events before the eventChan is setup (and those events will be lost) * Increased error level for logging watchers that lags * Updated discvoery_client to the latest API * Report discovery start errors * Update arduino/discovery/discovery_client/main.go Co-authored-by: Umberto Baldi <[email protected]> Co-authored-by: Umberto Baldi <[email protected]>
1 parent 312cfdb commit 9c334ed

File tree

10 files changed

+289
-404
lines changed

10 files changed

+289
-404
lines changed

Diff for: arduino/cores/packagemanager/package_manager_test.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -329,16 +329,14 @@ func TestPackageManagerClear(t *testing.T) {
329329
packageManager := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware, "test")
330330
packageManager.LoadHardwareFromDirectory(customHardware)
331331

332-
// Creates another PackageManager but don't load the hardware
333-
emptyPackageManager := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware, "test")
332+
// Check that the hardware is loaded
333+
require.NotEmpty(t, packageManager.Packages)
334334

335-
// Verifies they're not equal
336-
require.NotEqual(t, packageManager, emptyPackageManager)
337-
338-
// Clear the first PackageManager that contains loaded hardware
335+
// Clear the package manager
339336
packageManager.Clear()
340-
// Verifies both PackageManagers are now equal
341-
require.Equal(t, packageManager, emptyPackageManager)
337+
338+
// Check that the hardware is cleared
339+
require.Empty(t, packageManager.Packages)
342340
}
343341

344342
func TestFindToolsRequiredFromPlatformRelease(t *testing.T) {

Diff for: arduino/discovery/discovery.go

+18-36
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ type PluggableDiscovery struct {
5757
incomingMessagesError error
5858
state int
5959
eventChan chan<- *Event
60-
cachedPorts map[string]*Port
6160
}
6261

6362
type discoveryMessage struct {
@@ -121,8 +120,9 @@ func (p *Port) String() string {
121120

122121
// Event is a pluggable discovery event
123122
type Event struct {
124-
Type string
125-
Port *Port
123+
Type string
124+
Port *Port
125+
DiscoveryID string
126126
}
127127

128128
// New create and connect to the given pluggable discovery
@@ -131,7 +131,6 @@ func New(id string, args ...string) *PluggableDiscovery {
131131
id: id,
132132
processArgs: args,
133133
state: Dead,
134-
cachedPorts: map[string]*Port{},
135134
}
136135
}
137136

@@ -176,9 +175,8 @@ func (disc *PluggableDiscovery) jsonDecodeLoop(in io.Reader, outChan chan<- *dis
176175
return
177176
}
178177
disc.statusMutex.Lock()
179-
disc.cachedPorts[msg.Port.Address+"|"+msg.Port.Protocol] = msg.Port
180178
if disc.eventChan != nil {
181-
disc.eventChan <- &Event{"add", msg.Port}
179+
disc.eventChan <- &Event{"add", msg.Port, disc.GetID()}
182180
}
183181
disc.statusMutex.Unlock()
184182
} else if msg.EventType == "remove" {
@@ -187,9 +185,8 @@ func (disc *PluggableDiscovery) jsonDecodeLoop(in io.Reader, outChan chan<- *dis
187185
return
188186
}
189187
disc.statusMutex.Lock()
190-
delete(disc.cachedPorts, msg.Port.Address+"|"+msg.Port.Protocol)
191188
if disc.eventChan != nil {
192-
disc.eventChan <- &Event{"remove", msg.Port}
189+
disc.eventChan <- &Event{"remove", msg.Port, disc.GetID()}
193190
}
194191
disc.statusMutex.Unlock()
195192
} else {
@@ -276,10 +273,7 @@ func (disc *PluggableDiscovery) killProcess() error {
276273
}
277274
disc.statusMutex.Lock()
278275
defer disc.statusMutex.Unlock()
279-
if disc.eventChan != nil {
280-
close(disc.eventChan)
281-
disc.eventChan = nil
282-
}
276+
disc.stopSync()
283277
disc.state = Dead
284278
logrus.Infof("killed discovery %s process", disc.id)
285279
return nil
@@ -366,13 +360,17 @@ func (disc *PluggableDiscovery) Stop() error {
366360
}
367361
disc.statusMutex.Lock()
368362
defer disc.statusMutex.Unlock()
369-
disc.cachedPorts = map[string]*Port{}
363+
disc.stopSync()
364+
disc.state = Idling
365+
return nil
366+
}
367+
368+
func (disc *PluggableDiscovery) stopSync() {
370369
if disc.eventChan != nil {
370+
disc.eventChan <- &Event{"stop", nil, disc.GetID()}
371371
close(disc.eventChan)
372372
disc.eventChan = nil
373373
}
374-
disc.state = Idling
375-
return nil
376374
}
377375

378376
// Quit terminates the discovery. No more commands can be accepted by the discovery.
@@ -409,6 +407,9 @@ func (disc *PluggableDiscovery) List() ([]*Port, error) {
409407
// The event channel must be consumed as quickly as possible since it may block the
410408
// discovery if it becomes full. The channel size is configurable.
411409
func (disc *PluggableDiscovery) StartSync(size int) (<-chan *Event, error) {
410+
disc.statusMutex.Lock()
411+
defer disc.statusMutex.Unlock()
412+
412413
if err := disc.sendCommand("START_SYNC\n"); err != nil {
413414
return nil, err
414415
}
@@ -423,29 +424,10 @@ func (disc *PluggableDiscovery) StartSync(size int) (<-chan *Event, error) {
423424
return nil, errors.Errorf(tr("communication out of sync, expected '%[1]s', received '%[2]s'"), "OK", msg.Message)
424425
}
425426

426-
disc.statusMutex.Lock()
427-
defer disc.statusMutex.Unlock()
428427
disc.state = Syncing
429-
disc.cachedPorts = map[string]*Port{}
430-
if disc.eventChan != nil {
431-
// In case there is already an existing event channel in use we close it
432-
// before creating a new one.
433-
close(disc.eventChan)
434-
}
428+
// In case there is already an existing event channel in use we close it before creating a new one.
429+
disc.stopSync()
435430
c := make(chan *Event, size)
436431
disc.eventChan = c
437432
return c, nil
438433
}
439-
440-
// ListCachedPorts returns a list of the available ports. The list is a cache of all the
441-
// add/remove events happened from the StartSync call and it will not consume any
442-
// resource from the underliying discovery.
443-
func (disc *PluggableDiscovery) ListCachedPorts() []*Port {
444-
disc.statusMutex.Lock()
445-
defer disc.statusMutex.Unlock()
446-
res := []*Port{}
447-
for _, port := range disc.cachedPorts {
448-
res = append(res, port)
449-
}
450-
return res
451-
}

Diff for: arduino/discovery/discovery_client/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ replace github.com/arduino/arduino-cli => ../../..
77
require (
88
github.com/arduino/arduino-cli v0.0.0-00010101000000-000000000000
99
github.com/gizak/termui/v3 v3.1.0
10+
github.com/sirupsen/logrus v1.4.2
1011
)
1112

1213
require (
@@ -20,7 +21,6 @@ require (
2021
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
2122
github.com/pkg/errors v0.9.1 // indirect
2223
github.com/rivo/uniseg v0.2.0 // indirect
23-
github.com/sirupsen/logrus v1.4.2 // indirect
2424
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect
2525
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
2626
golang.org/x/text v0.3.6 // indirect

Diff for: arduino/discovery/discovery_client/main.go

+33-40
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,28 @@ import (
2121
"log"
2222
"os"
2323
"sort"
24-
"time"
2524

2625
"github.com/arduino/arduino-cli/arduino/discovery"
26+
"github.com/arduino/arduino-cli/arduino/discovery/discoverymanager"
2727
ui "github.com/gizak/termui/v3"
2828
"github.com/gizak/termui/v3/widgets"
29+
"github.com/sirupsen/logrus"
2930
)
3031

3132
func main() {
32-
discoveries := []*discovery.PluggableDiscovery{}
33-
discEvent := make(chan *discovery.Event)
33+
logrus.SetLevel(logrus.ErrorLevel)
34+
dm := discoverymanager.New()
3435
for _, discCmd := range os.Args[1:] {
35-
disc := discovery.New("", discCmd)
36-
if err := disc.Run(); err != nil {
37-
log.Fatal("Error starting discovery:", err)
38-
}
39-
if err := disc.Start(); err != nil {
40-
log.Fatal("Error starting discovery:", err)
41-
}
42-
eventChan, err := disc.StartSync(10)
43-
if err != nil {
44-
log.Fatal("Error starting discovery:", err)
45-
}
46-
go func() {
47-
for msg := range eventChan {
48-
discEvent <- msg
49-
}
50-
}()
51-
discoveries = append(discoveries, disc)
36+
disc := discovery.New(discCmd, discCmd)
37+
dm.Add(disc)
5238
}
39+
dm.Start()
5340

41+
activePorts := map[string]*discovery.Port{}
42+
watcher, err := dm.Watch()
43+
if err != nil {
44+
log.Fatalf("failed to start discoveries: %v", err)
45+
}
5446
if err := ui.Init(); err != nil {
5547
log.Fatalf("failed to initialize termui: %v", err)
5648
}
@@ -66,15 +58,20 @@ func main() {
6658
updateList := func() {
6759
rows := []string{}
6860
rows = append(rows, "Available ports list:")
69-
for _, disc := range discoveries {
70-
for i, port := range disc.ListCachedPorts() {
71-
rows = append(rows, fmt.Sprintf(" [%04d] Address: %s", i, port.AddressLabel))
72-
rows = append(rows, fmt.Sprintf(" Protocol: %s", port.ProtocolLabel))
73-
keys := port.Properties.Keys()
74-
sort.Strings(keys)
75-
for _, k := range keys {
76-
rows = append(rows, fmt.Sprintf(" %s=%s", k, port.Properties.Get(k)))
77-
}
61+
62+
ids := sort.StringSlice{}
63+
for id := range activePorts {
64+
ids = append(ids, id)
65+
}
66+
ids.Sort()
67+
for _, id := range ids {
68+
port := activePorts[id]
69+
rows = append(rows, fmt.Sprintf("> Address: %s", port.AddressLabel))
70+
rows = append(rows, fmt.Sprintf(" Protocol: %s", port.ProtocolLabel))
71+
keys := port.Properties.Keys()
72+
sort.Strings(keys)
73+
for _, k := range keys {
74+
rows = append(rows, fmt.Sprintf(" %s=%s", k, port.Properties.Get(k)))
7875
}
7976
}
8077
l.Rows = rows
@@ -123,20 +120,16 @@ out:
123120
previousKey = e.ID
124121
}
125122

126-
case <-discEvent:
123+
case ev := <-watcher.Feed():
124+
if ev.Type == "add" {
125+
activePorts[ev.Port.Address+"|"+ev.Port.Protocol] = ev.Port
126+
}
127+
if ev.Type == "remove" {
128+
delete(activePorts, ev.Port.Address+"|"+ev.Port.Protocol)
129+
}
127130
updateList()
128131
}
129132

130133
ui.Render(l)
131134
}
132-
133-
for _, disc := range discoveries {
134-
disc.Quit()
135-
fmt.Println("Discovery QUITed")
136-
for disc.State() == discovery.Alive {
137-
time.Sleep(time.Millisecond)
138-
}
139-
fmt.Println("Discovery correctly terminated")
140-
}
141-
142135
}

0 commit comments

Comments
 (0)