Skip to content

Rewrite the serial discovery logic #382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions commands/board/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,7 @@ func List(instanceID int32) ([]*rpc.DetectedPort, error) {
return nil, errors.New("invalid instance")
}

serialDiscovery, err := commands.NewBuiltinSerialDiscovery(pm)
if err != nil {
return nil, errors.Wrap(err, "unable to instance serial-discovery")
}

ports, err := serialDiscovery.List()
ports, err := commands.ListBoards(pm)
if err != nil {
return nil, errors.Wrap(err, "error getting port list from serial-discovery")
}
Expand Down
132 changes: 53 additions & 79 deletions commands/bundled_tools_serial_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ package commands
import (
"encoding/json"
"fmt"
"io"
"os/exec"
"strings"
"sync"
"time"

Expand All @@ -36,6 +33,7 @@ import (
)

var (
mutex = sync.Mutex{}
sdVersion = semver.ParseRelaxed("1.0.0")
flavors = []*cores.Flavor{
{
Expand Down Expand Up @@ -101,16 +99,6 @@ var (
}
)

// SerialDiscovery is an instance of a discovery tool
type SerialDiscovery struct {
sync.Mutex
ID string
in io.WriteCloser
out io.ReadCloser
outJSON *json.Decoder
cmd *exec.Cmd
}

// BoardPort is a generic port descriptor
type BoardPort struct {
Address string `json:"address"`
Expand All @@ -126,100 +114,86 @@ type eventJSON struct {
Ports []*BoardPort `json:"ports"`
}

// NewBuiltinSerialDiscovery returns a wrapper to control the serial-discovery program
func NewBuiltinSerialDiscovery(pm *packagemanager.PackageManager) (*SerialDiscovery, error) {
// ListBoards foo
func ListBoards(pm *packagemanager.PackageManager) ([]*BoardPort, error) {
// ensure the connection to the discoverer is unique to avoid messing up
// the messages exchanged
mutex.Lock()
defer mutex.Unlock()

// get the bundled tool
t, err := getBuiltinSerialDiscoveryTool(pm)
if err != nil {
return nil, err
}

// determine if it's installed
if !t.IsInstalled() {
return nil, fmt.Errorf("missing serial-discovery tool")
}

cmdArgs := []string{
t.InstallDir.Join("serial-discovery").String(),
}

cmd, err := executils.Command(cmdArgs)
// build the command to be executed
args := []string{t.InstallDir.Join("serial-discovery").String()}
cmd, err := executils.Command(args)
if err != nil {
return nil, errors.Wrap(err, "creating discovery process")
}

return &SerialDiscovery{
ID: strings.Join(cmdArgs, " "),
cmd: cmd,
}, nil
}

// Start starts the specified discovery
func (d *SerialDiscovery) start() error {
if in, err := d.cmd.StdinPipe(); err == nil {
d.in = in
} else {
return fmt.Errorf("creating stdin pipe for discovery: %s", err)
}

if out, err := d.cmd.StdoutPipe(); err == nil {
d.out = out
d.outJSON = json.NewDecoder(d.out)
} else {
return fmt.Errorf("creating stdout pipe for discovery: %s", err)
// attach in/out pipes to the process
in, err := cmd.StdinPipe()
if err != nil {
return nil, fmt.Errorf("creating stdin pipe for discovery: %s", err)
}

if err := d.cmd.Start(); err != nil {
return fmt.Errorf("starting discovery process: %s", err)
out, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("creating stdout pipe for discovery: %s", err)
}
outJSON := json.NewDecoder(out)

return nil
}

// List retrieve the port list from this discovery
func (d *SerialDiscovery) List() ([]*BoardPort, error) {
// ensure the connection to the discoverer is unique to avoid messing up
// the messages exchanged
d.Lock()
defer d.Unlock()

if err := d.start(); err != nil {
return nil, fmt.Errorf("discovery hasn't started: %v", err)
// start the process
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("starting discovery process: %s", err)
}

if _, err := d.in.Write([]byte("LIST\n")); err != nil {
// send the LIST command
if _, err := in.Write([]byte("LIST\n")); err != nil {
return nil, fmt.Errorf("sending LIST command to discovery: %s", err)
}

// read the response from the pipe
decodeResult := make(chan error)
var event eventJSON
done := make(chan bool)
timeout := false
go func() {
select {
case <-done:
case <-time.After(10 * time.Second):
timeout = true
d.close()
}
decodeResult <- outJSON.Decode(&event)
}()
if err := d.outJSON.Decode(&event); err != nil {
if timeout {
return nil, fmt.Errorf("decoding LIST command: timeout")

var finalError error
var retVal []*BoardPort

// wait for the response
select {
case err := <-decodeResult:
if err == nil {
retVal = event.Ports
} else {
finalError = err
}
return nil, fmt.Errorf("decoding LIST command: %s", err)
case <-time.After(10 * time.Second):
finalError = fmt.Errorf("decoding LIST command: timeout")
}
done <- true
return event.Ports, d.close()
}

// Close stops the Discovery and free the resources
func (d *SerialDiscovery) close() error {
_, _ = d.in.Write([]byte("QUIT\n"))
_ = d.in.Close()
_ = d.out.Close()
timer := time.AfterFunc(time.Second, func() {
_ = d.cmd.Process.Kill()
// tell the process to quit
in.Write([]byte("QUIT\n"))
in.Close()
out.Close()
// kill the process if it takes too long to quit
time.AfterFunc(time.Second, func() {
cmd.Process.Kill()
})
err := d.cmd.Wait()
_ = timer.Stop()
return err
cmd.Wait()

return retVal, finalError
}

func getBuiltinSerialDiscoveryTool(pm *packagemanager.PackageManager) (*cores.ToolRelease, error) {
Expand Down