Unverified Commit 9c334ed8 authored by Cristian Maglie's avatar Cristian Maglie Committed by GitHub

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: default avatarUmberto Baldi <34278123+umbynos@users.noreply.github.com>
Co-authored-by: default avatarUmberto Baldi <34278123+umbynos@users.noreply.github.com>
parent 312cfdb9
...@@ -329,16 +329,14 @@ func TestPackageManagerClear(t *testing.T) { ...@@ -329,16 +329,14 @@ func TestPackageManagerClear(t *testing.T) {
packageManager := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware, "test") packageManager := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware, "test")
packageManager.LoadHardwareFromDirectory(customHardware) packageManager.LoadHardwareFromDirectory(customHardware)
// Creates another PackageManager but don't load the hardware // Check that the hardware is loaded
emptyPackageManager := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware, "test") require.NotEmpty(t, packageManager.Packages)
// Verifies they're not equal // Clear the package manager
require.NotEqual(t, packageManager, emptyPackageManager)
// Clear the first PackageManager that contains loaded hardware
packageManager.Clear() packageManager.Clear()
// Verifies both PackageManagers are now equal
require.Equal(t, packageManager, emptyPackageManager) // Check that the hardware is cleared
require.Empty(t, packageManager.Packages)
} }
func TestFindToolsRequiredFromPlatformRelease(t *testing.T) { func TestFindToolsRequiredFromPlatformRelease(t *testing.T) {
......
...@@ -57,7 +57,6 @@ type PluggableDiscovery struct { ...@@ -57,7 +57,6 @@ type PluggableDiscovery struct {
incomingMessagesError error incomingMessagesError error
state int state int
eventChan chan<- *Event eventChan chan<- *Event
cachedPorts map[string]*Port
} }
type discoveryMessage struct { type discoveryMessage struct {
...@@ -121,8 +120,9 @@ func (p *Port) String() string { ...@@ -121,8 +120,9 @@ func (p *Port) String() string {
// Event is a pluggable discovery event // Event is a pluggable discovery event
type Event struct { type Event struct {
Type string Type string
Port *Port Port *Port
DiscoveryID string
} }
// New create and connect to the given pluggable discovery // New create and connect to the given pluggable discovery
...@@ -131,7 +131,6 @@ func New(id string, args ...string) *PluggableDiscovery { ...@@ -131,7 +131,6 @@ func New(id string, args ...string) *PluggableDiscovery {
id: id, id: id,
processArgs: args, processArgs: args,
state: Dead, state: Dead,
cachedPorts: map[string]*Port{},
} }
} }
...@@ -176,9 +175,8 @@ func (disc *PluggableDiscovery) jsonDecodeLoop(in io.Reader, outChan chan<- *dis ...@@ -176,9 +175,8 @@ func (disc *PluggableDiscovery) jsonDecodeLoop(in io.Reader, outChan chan<- *dis
return return
} }
disc.statusMutex.Lock() disc.statusMutex.Lock()
disc.cachedPorts[msg.Port.Address+"|"+msg.Port.Protocol] = msg.Port
if disc.eventChan != nil { if disc.eventChan != nil {
disc.eventChan <- &Event{"add", msg.Port} disc.eventChan <- &Event{"add", msg.Port, disc.GetID()}
} }
disc.statusMutex.Unlock() disc.statusMutex.Unlock()
} else if msg.EventType == "remove" { } else if msg.EventType == "remove" {
...@@ -187,9 +185,8 @@ func (disc *PluggableDiscovery) jsonDecodeLoop(in io.Reader, outChan chan<- *dis ...@@ -187,9 +185,8 @@ func (disc *PluggableDiscovery) jsonDecodeLoop(in io.Reader, outChan chan<- *dis
return return
} }
disc.statusMutex.Lock() disc.statusMutex.Lock()
delete(disc.cachedPorts, msg.Port.Address+"|"+msg.Port.Protocol)
if disc.eventChan != nil { if disc.eventChan != nil {
disc.eventChan <- &Event{"remove", msg.Port} disc.eventChan <- &Event{"remove", msg.Port, disc.GetID()}
} }
disc.statusMutex.Unlock() disc.statusMutex.Unlock()
} else { } else {
...@@ -276,10 +273,7 @@ func (disc *PluggableDiscovery) killProcess() error { ...@@ -276,10 +273,7 @@ func (disc *PluggableDiscovery) killProcess() error {
} }
disc.statusMutex.Lock() disc.statusMutex.Lock()
defer disc.statusMutex.Unlock() defer disc.statusMutex.Unlock()
if disc.eventChan != nil { disc.stopSync()
close(disc.eventChan)
disc.eventChan = nil
}
disc.state = Dead disc.state = Dead
logrus.Infof("killed discovery %s process", disc.id) logrus.Infof("killed discovery %s process", disc.id)
return nil return nil
...@@ -366,13 +360,17 @@ func (disc *PluggableDiscovery) Stop() error { ...@@ -366,13 +360,17 @@ func (disc *PluggableDiscovery) Stop() error {
} }
disc.statusMutex.Lock() disc.statusMutex.Lock()
defer disc.statusMutex.Unlock() defer disc.statusMutex.Unlock()
disc.cachedPorts = map[string]*Port{} disc.stopSync()
disc.state = Idling
return nil
}
func (disc *PluggableDiscovery) stopSync() {
if disc.eventChan != nil { if disc.eventChan != nil {
disc.eventChan <- &Event{"stop", nil, disc.GetID()}
close(disc.eventChan) close(disc.eventChan)
disc.eventChan = nil disc.eventChan = nil
} }
disc.state = Idling
return nil
} }
// Quit terminates the discovery. No more commands can be accepted by the discovery. // Quit terminates the discovery. No more commands can be accepted by the discovery.
...@@ -409,6 +407,9 @@ func (disc *PluggableDiscovery) List() ([]*Port, error) { ...@@ -409,6 +407,9 @@ func (disc *PluggableDiscovery) List() ([]*Port, error) {
// The event channel must be consumed as quickly as possible since it may block the // The event channel must be consumed as quickly as possible since it may block the
// discovery if it becomes full. The channel size is configurable. // discovery if it becomes full. The channel size is configurable.
func (disc *PluggableDiscovery) StartSync(size int) (<-chan *Event, error) { func (disc *PluggableDiscovery) StartSync(size int) (<-chan *Event, error) {
disc.statusMutex.Lock()
defer disc.statusMutex.Unlock()
if err := disc.sendCommand("START_SYNC\n"); err != nil { if err := disc.sendCommand("START_SYNC\n"); err != nil {
return nil, err return nil, err
} }
...@@ -423,29 +424,10 @@ func (disc *PluggableDiscovery) StartSync(size int) (<-chan *Event, error) { ...@@ -423,29 +424,10 @@ func (disc *PluggableDiscovery) StartSync(size int) (<-chan *Event, error) {
return nil, errors.Errorf(tr("communication out of sync, expected '%[1]s', received '%[2]s'"), "OK", msg.Message) return nil, errors.Errorf(tr("communication out of sync, expected '%[1]s', received '%[2]s'"), "OK", msg.Message)
} }
disc.statusMutex.Lock()
defer disc.statusMutex.Unlock()
disc.state = Syncing disc.state = Syncing
disc.cachedPorts = map[string]*Port{} // In case there is already an existing event channel in use we close it before creating a new one.
if disc.eventChan != nil { disc.stopSync()
// In case there is already an existing event channel in use we close it
// before creating a new one.
close(disc.eventChan)
}
c := make(chan *Event, size) c := make(chan *Event, size)
disc.eventChan = c disc.eventChan = c
return c, nil return c, nil
} }
// ListCachedPorts returns a list of the available ports. The list is a cache of all the
// add/remove events happened from the StartSync call and it will not consume any
// resource from the underliying discovery.
func (disc *PluggableDiscovery) ListCachedPorts() []*Port {
disc.statusMutex.Lock()
defer disc.statusMutex.Unlock()
res := []*Port{}
for _, port := range disc.cachedPorts {
res = append(res, port)
}
return res
}
...@@ -7,6 +7,7 @@ replace github.com/arduino/arduino-cli => ../../.. ...@@ -7,6 +7,7 @@ replace github.com/arduino/arduino-cli => ../../..
require ( require (
github.com/arduino/arduino-cli v0.0.0-00010101000000-000000000000 github.com/arduino/arduino-cli v0.0.0-00010101000000-000000000000
github.com/gizak/termui/v3 v3.1.0 github.com/gizak/termui/v3 v3.1.0
github.com/sirupsen/logrus v1.4.2
) )
require ( require (
...@@ -20,7 +21,6 @@ require ( ...@@ -20,7 +21,6 @@ require (
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.6 // indirect golang.org/x/text v0.3.6 // indirect
......
...@@ -21,36 +21,28 @@ import ( ...@@ -21,36 +21,28 @@ import (
"log" "log"
"os" "os"
"sort" "sort"
"time"
"github.com/arduino/arduino-cli/arduino/discovery" "github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/arduino/discovery/discoverymanager"
ui "github.com/gizak/termui/v3" ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets" "github.com/gizak/termui/v3/widgets"
"github.com/sirupsen/logrus"
) )
func main() { func main() {
discoveries := []*discovery.PluggableDiscovery{} logrus.SetLevel(logrus.ErrorLevel)
discEvent := make(chan *discovery.Event) dm := discoverymanager.New()
for _, discCmd := range os.Args[1:] { for _, discCmd := range os.Args[1:] {
disc := discovery.New("", discCmd) disc := discovery.New(discCmd, discCmd)
if err := disc.Run(); err != nil { dm.Add(disc)
log.Fatal("Error starting discovery:", err)
}
if err := disc.Start(); err != nil {
log.Fatal("Error starting discovery:", err)
}
eventChan, err := disc.StartSync(10)
if err != nil {
log.Fatal("Error starting discovery:", err)
}
go func() {
for msg := range eventChan {
discEvent <- msg
}
}()
discoveries = append(discoveries, disc)
} }
dm.Start()
activePorts := map[string]*discovery.Port{}
watcher, err := dm.Watch()
if err != nil {
log.Fatalf("failed to start discoveries: %v", err)
}
if err := ui.Init(); err != nil { if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err) log.Fatalf("failed to initialize termui: %v", err)
} }
...@@ -66,15 +58,20 @@ func main() { ...@@ -66,15 +58,20 @@ func main() {
updateList := func() { updateList := func() {
rows := []string{} rows := []string{}
rows = append(rows, "Available ports list:") rows = append(rows, "Available ports list:")
for _, disc := range discoveries {
for i, port := range disc.ListCachedPorts() { ids := sort.StringSlice{}
rows = append(rows, fmt.Sprintf(" [%04d] Address: %s", i, port.AddressLabel)) for id := range activePorts {
rows = append(rows, fmt.Sprintf(" Protocol: %s", port.ProtocolLabel)) ids = append(ids, id)
keys := port.Properties.Keys() }
sort.Strings(keys) ids.Sort()
for _, k := range keys { for _, id := range ids {
rows = append(rows, fmt.Sprintf(" %s=%s", k, port.Properties.Get(k))) port := activePorts[id]
} rows = append(rows, fmt.Sprintf("> Address: %s", port.AddressLabel))
rows = append(rows, fmt.Sprintf(" Protocol: %s", port.ProtocolLabel))
keys := port.Properties.Keys()
sort.Strings(keys)
for _, k := range keys {
rows = append(rows, fmt.Sprintf(" %s=%s", k, port.Properties.Get(k)))
} }
} }
l.Rows = rows l.Rows = rows
...@@ -123,20 +120,16 @@ out: ...@@ -123,20 +120,16 @@ out:
previousKey = e.ID previousKey = e.ID
} }
case <-discEvent: case ev := <-watcher.Feed():
if ev.Type == "add" {
activePorts[ev.Port.Address+"|"+ev.Port.Protocol] = ev.Port
}
if ev.Type == "remove" {
delete(activePorts, ev.Port.Address+"|"+ev.Port.Protocol)
}
updateList() updateList()
} }
ui.Render(l) ui.Render(l)
} }
for _, disc := range discoveries {
disc.Quit()
fmt.Println("Discovery QUITed")
for disc.State() == discovery.Alive {
time.Sleep(time.Millisecond)
}
fmt.Println("Discovery correctly terminated")
}
} }
...@@ -178,7 +178,7 @@ func GetInstallableLibs() []string { ...@@ -178,7 +178,7 @@ func GetInstallableLibs() []string {
func GetConnectedBoards() []string { func GetConnectedBoards() []string {
inst := instance.CreateAndInit() inst := instance.CreateAndInit()
list, _ := board.List(&rpc.BoardListRequest{ list, _, _ := board.List(&rpc.BoardListRequest{
Instance: inst, Instance: inst,
}) })
var res []string var res []string
......
...@@ -106,31 +106,16 @@ func (p *Port) GetPort(instance *rpc.Instance, sk *sketch.Sketch) (*discovery.Po ...@@ -106,31 +106,16 @@ func (p *Port) GetPort(instance *rpc.Instance, sk *sketch.Sketch) (*discovery.Po
return nil, errors.New("invalid instance") return nil, errors.New("invalid instance")
} }
dm := pm.DiscoveryManager() dm := pm.DiscoveryManager()
if errs := dm.RunAll(); len(errs) == len(dm.IDs()) { watcher, err := dm.Watch()
// All discoveries failed to run, we can't do anything if err != nil {
return nil, fmt.Errorf("%v", errs) return nil, err
} else if len(errs) > 0 {
// If only some discoveries failed to run just tell the user and go on
for _, err := range errs {
feedback.Error(err)
}
}
eventChan, errs := dm.StartSyncAll()
if len(errs) > 0 {
return nil, fmt.Errorf("%v", errs)
} }
defer watcher.Close()
defer func() {
// Quit all discoveries at the end.
if errs := dm.QuitAll(); len(errs) > 0 {
logrus.Errorf("quitting discoveries when getting port metadata: %v", errs)
}
}()
deadline := time.After(p.timeout.Get()) deadline := time.After(p.timeout.Get())
for { for {
select { select {
case portEvent := <-eventChan: case portEvent := <-watcher.Feed():
if portEvent.Type != "add" { if portEvent.Type != "add" {
continue continue
} }
...@@ -161,7 +146,7 @@ func (p *Port) GetSearchTimeout() time.Duration { ...@@ -161,7 +146,7 @@ func (p *Port) GetSearchTimeout() time.Duration {
// discovered Port object together with the FQBN. If the port does not match // discovered Port object together with the FQBN. If the port does not match
// exactly 1 board, // exactly 1 board,
func (p *Port) DetectFQBN(inst *rpc.Instance) (string, *rpc.Port) { func (p *Port) DetectFQBN(inst *rpc.Instance) (string, *rpc.Port) {
detectedPorts, err := board.List(&rpc.BoardListRequest{ detectedPorts, _, err := board.List(&rpc.BoardListRequest{
Instance: inst, Instance: inst,
Timeout: p.timeout.Get().Milliseconds(), Timeout: p.timeout.Get().Milliseconds(),
}) })
......
...@@ -64,22 +64,26 @@ func runListCommand(cmd *cobra.Command, args []string) { ...@@ -64,22 +64,26 @@ func runListCommand(cmd *cobra.Command, args []string) {
os.Exit(0) os.Exit(0)
} }
ports, err := board.List(&rpc.BoardListRequest{ ports, discvoeryErrors, err := board.List(&rpc.BoardListRequest{
Instance: inst, Instance: inst,
Timeout: timeoutArg.Get().Milliseconds(), Timeout: timeoutArg.Get().Milliseconds(),
}) })
if err != nil { if err != nil {
feedback.Errorf(tr("Error detecting boards: %v"), err) feedback.Errorf(tr("Error detecting boards: %v"), err)
} }
for _, err := range discvoeryErrors {
feedback.Errorf(tr("Error starting discovery: %v"), err)
}
feedback.PrintResult(result{ports}) feedback.PrintResult(result{ports})
} }
func watchList(cmd *cobra.Command, inst *rpc.Instance) { func watchList(cmd *cobra.Command, inst *rpc.Instance) {
eventsChan, err := board.Watch(inst.Id, nil) eventsChan, closeCB, err := board.Watch(inst.Id)
if err != nil { if err != nil {
feedback.Errorf(tr("Error detecting boards: %v"), err) feedback.Errorf(tr("Error detecting boards: %v"), err)
os.Exit(errorcodes.ErrNetwork) os.Exit(errorcodes.ErrNetwork)
} }
defer closeCB()
// This is done to avoid printing the header each time a new event is received // This is done to avoid printing the header each time a new event is received
if feedback.GetFormat() == feedback.Text { if feedback.GetFormat() == feedback.Text {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package board package board
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
...@@ -176,32 +177,21 @@ func identify(pm *packagemanager.PackageManager, port *discovery.Port) ([]*rpc.B ...@@ -176,32 +177,21 @@ func identify(pm *packagemanager.PackageManager, port *discovery.Port) ([]*rpc.B
// List returns a list of boards found by the loaded discoveries. // List returns a list of boards found by the loaded discoveries.
// In case of errors partial results from discoveries that didn't fail // In case of errors partial results from discoveries that didn't fail
// are returned. // are returned.
func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, e error) { func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, discoveryStartErrors []error, e error) {
pm := commands.GetPackageManager(req.GetInstance().Id) pm := commands.GetPackageManager(req.GetInstance().Id)
if pm == nil { if pm == nil {
return nil, &arduino.InvalidInstanceError{} return nil, nil, &arduino.InvalidInstanceError{}
} }
dm := pm.DiscoveryManager() dm := pm.DiscoveryManager()
if errs := dm.RunAll(); len(errs) > 0 { discoveryStartErrors = dm.Start()
return nil, &arduino.UnavailableError{Message: tr("Error starting board discoveries"), Cause: fmt.Errorf("%v", errs)}
}
if errs := dm.StartAll(); len(errs) > 0 {
return nil, &arduino.UnavailableError{Message: tr("Error starting board discoveries"), Cause: fmt.Errorf("%v", errs)}
}
defer func() {
if errs := dm.StopAll(); len(errs) > 0 {
logrus.Error(errs)
}
}()
time.Sleep(time.Duration(req.GetTimeout()) * time.Millisecond) time.Sleep(time.Duration(req.GetTimeout()) * time.Millisecond)
retVal := []*rpc.DetectedPort{} retVal := []*rpc.DetectedPort{}
ports, errs := pm.DiscoveryManager().List() for _, port := range dm.List() {
for _, port := range ports {
boards, err := identify(pm, port) boards, err := identify(pm, port)
if err != nil { if err != nil {
return nil, err return nil, discoveryStartErrors, err
} }
// boards slice can be empty at this point if neither the cores nor the // boards slice can be empty at this point if neither the cores nor the
...@@ -212,92 +202,49 @@ func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, e error) { ...@@ -212,92 +202,49 @@ func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, e error) {
} }
retVal = append(retVal, b) retVal = append(retVal, b)
} }
if len(errs) > 0 { return retVal, discoveryStartErrors, nil
return retVal, &arduino.UnavailableError{Message: tr("Error getting board list"), Cause: fmt.Errorf("%v", errs)}
}
return retVal, nil
} }
// Watch returns a channel that receives boards connection and disconnection events. // Watch returns a channel that receives boards connection and disconnection events.
// The discovery process can be interrupted by sending a message to the interrupt channel. // It also returns a callback function that must be used to stop and dispose the watch.
func Watch(instanceID int32, interrupt <-chan bool) (<-chan *rpc.BoardListWatchResponse, error) { func Watch(instanceID int32) (<-chan *rpc.BoardListWatchResponse, func(), error) {
pm := commands.GetPackageManager(instanceID) pm := commands.GetPackageManager(instanceID)
dm := pm.DiscoveryManager() dm := pm.DiscoveryManager()
runErrs := dm.RunAll() watcher, err := dm.Watch()
if len(runErrs) == len(dm.IDs()) { if err != nil {
// All discoveries failed to run, we can't do anything return nil, nil, err
return nil, &arduino.UnavailableError{Message: tr("Error starting board discoveries"), Cause: fmt.Errorf("%v", runErrs)}
} }
eventsChan, errs := dm.StartSyncAll() ctx, cancel := context.WithCancel(context.Background())
if len(runErrs) > 0 { go func() {
errs = append(runErrs, errs...) <-ctx.Done()
} watcher.Close()
}()
outChan := make(chan *rpc.BoardListWatchResponse) outChan := make(chan *rpc.BoardListWatchResponse)
go func() { go func() {
defer close(outChan) defer close(outChan)
for _, err := range errs { for event := range watcher.Feed() {
outChan <- &rpc.BoardListWatchResponse{ port := &rpc.DetectedPort{
EventType: "error", Port: event.Port.ToRPC(),
Error: err.Error(),
} }
}
for {
select {
case event := <-eventsChan:
if event.Type == "quit" {
// The discovery manager has closed its event channel because it's
// quitting all the discovery processes that are running, this
// means that the events channel we're listening from won't receive any
// more events.
// Handling this case is necessary when the board watcher is running and
// the instance being used is reinitialized since that quits all the
// discovery processes and reset the discovery manager. That would leave
// this goroutine listening forever on a "dead" channel and might even
// cause panics.
// This message avoid all this issues.
// It will be the client's task restarting the board watcher if necessary,
// this host won't attempt restarting it.
outChan <- &rpc.BoardListWatchResponse{
EventType: event.Type,
}
return
}
port := &rpc.DetectedPort{
Port: event.Port.ToRPC(),
}
boardsError := "" boardsError := ""
if event.Type == "add" { if event.Type == "add" {
boards, err := identify(pm, event.Port) boards, err := identify(pm, event.Port)
if err != nil { if err != nil {
boardsError = err.Error() boardsError = err.Error()
}
port.MatchingBoards = boards
}
outChan <- &rpc.BoardListWatchResponse{
EventType: event.Type,
Port: port,
Error: boardsError,
}
case <-interrupt:
for _, err := range dm.StopAll() {
// Discoveries that return errors have their process
// closed and are removed from the list of discoveries
// in the manager
outChan <- &rpc.BoardListWatchResponse{
EventType: "error",
Error: tr("stopping discoveries: %s", err),
}
} }
return port.MatchingBoards = boards
}
outChan <- &rpc.BoardListWatchResponse{
EventType: event.Type,
Port: port,
Error: boardsError,
} }
} }
}() }()
return outChan, nil return outChan, cancel, nil
} }
...@@ -36,9 +36,7 @@ import ( ...@@ -36,9 +36,7 @@ import (
"github.com/arduino/arduino-cli/i18n" "github.com/arduino/arduino-cli/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
) )
// ArduinoCoreServerImpl FIXMEDOC // ArduinoCoreServerImpl FIXMEDOC
...@@ -69,7 +67,7 @@ func (s *ArduinoCoreServerImpl) BoardDetails(ctx context.Context, req *rpc.Board ...@@ -69,7 +67,7 @@ func (s *ArduinoCoreServerImpl) BoardDetails(ctx context.Context, req *rpc.Board
// BoardList FIXMEDOC // BoardList FIXMEDOC
func (s *ArduinoCoreServerImpl) BoardList(ctx context.Context, req *rpc.BoardListRequest) (*rpc.BoardListResponse, error) { func (s *ArduinoCoreServerImpl) BoardList(ctx context.Context, req *rpc.BoardListRequest) (*rpc.BoardListResponse, error) {
ports, err := board.List(req) ports, _, err := board.List(req)
if err != nil { if err != nil {
return nil, convertErrorToRPCStatus(err) return nil, convertErrorToRPCStatus(err)
} }
...@@ -109,42 +107,35 @@ func (s *ArduinoCoreServerImpl) BoardListWatch(stream rpc.ArduinoCoreService_Boa ...@@ -109,42 +107,35 @@ func (s *ArduinoCoreServerImpl) BoardListWatch(stream rpc.ArduinoCoreService_Boa
return err return err
} }
interrupt := make(chan bool, 1) eventsChan, closeWatcher, err := board.Watch(msg.Instance.Id)
if err != nil {
return convertErrorToRPCStatus(err)
}
go func() { go func() {
defer close(interrupt) defer closeWatcher()
for { for {
msg, err := stream.Recv() msg, err := stream.Recv()
// Handle client closing the stream and eventual errors // Handle client closing the stream and eventual errors
if err == io.EOF { if err == io.EOF {
logrus.Info("boards watcher stream closed") logrus.Info("boards watcher stream closed")
interrupt <- true
return
} else if st, ok := status.FromError(err); ok && st.Code() == codes.Canceled {
logrus.Info("boards watcher interrupted by host")
return return
} else if err != nil { }
if err != nil {
logrus.Infof("interrupting boards watcher: %v", err) logrus.Infof("interrupting boards watcher: %v", err)
interrupt <- true
return return
} }
// Message received, does the client want to interrupt? // Message received, does the client want to interrupt?
if msg != nil && msg.Interrupt { if msg != nil && msg.Interrupt {
logrus.Info("boards watcher interrupted by client") logrus.Info("boards watcher interrupted by client")
interrupt <- msg.Interrupt
return return
} }
} }
}() }()
eventsChan, err := board.Watch(msg.Instance.Id, interrupt)
if err != nil {
return convertErrorToRPCStatus(err)
}
for event := range eventsChan { for event := range eventsChan {
err = stream.Send(event) if err := stream.Send(event); err != nil {
if err != nil {
logrus.Infof("sending board watch message: %v", err) logrus.Infof("sending board watch message: %v", err)
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment