Skip to content

Commit

Permalink
Fix: errors on 'exit' and signal handling / Feature: node panel in GUI (
Browse files Browse the repository at this point in the history
#70)

* [cli][pylibs] Bugfix for OTNS 'exit' command.

* [cli][cmd][dispatcher][otns_main][otnstester][simulation][visualize] harmonized the 'exit' debug messages; bugfix that CLI Close() could lockup.

* [web] added node info panel textarea. It shows also last Tx power and Tx channel based on the info received from the 'Send' push event.

* [cli] try to avoid blocking on CLI close

* [all] better staged starting of webserver and other components. This solves the issue that webserver was being started still while rest of program was already done exiting.

* [all] remove unused code, minor updates.
  • Loading branch information
EskoDijk committed Sep 24, 2024
1 parent 0dc84ed commit eb666f3
Show file tree
Hide file tree
Showing 33 changed files with 719 additions and 439 deletions.
98 changes: 45 additions & 53 deletions cli/CmdRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@ import (
"strings"
"time"

"github.com/pkg/errors"
"github.com/simonlingoogle/go-simplelogger"
"gopkg.in/yaml.v3"

"github.com/openthread/ot-ns/dispatcher"
"github.com/openthread/ot-ns/progctx"
"github.com/openthread/ot-ns/radiomodel"
"github.com/openthread/ot-ns/simulation"
. "github.com/openthread/ot-ns/types"
"github.com/openthread/ot-ns/visualize"
"github.com/openthread/ot-ns/web"
"github.com/pkg/errors"
"github.com/simonlingoogle/go-simplelogger"
"gopkg.in/yaml.v3"
)

const (
Expand Down Expand Up @@ -103,45 +104,54 @@ type CmdRunner struct {
help Help
}

func (rt *CmdRunner) RunCommand(cmdline string, output io.Writer) error {
if rt.ctx.Err() != nil {
return nil
}
// if character '!' is used to invoke no-node (global) context, remove it.
if len(cmdline) > 1 && cmdline[0] == '!' {
cmdline = cmdline[1:]
func NewCmdRunner(ctx *progctx.ProgCtx, sim *simulation.Simulation) *CmdRunner {
cr := &CmdRunner{
ctx: ctx,
sim: sim,
contextNodeId: InvalidNodeId,
help: newHelp(),
}
// run the OTNS-CLI command without node contexts
cmd := Command{}
sim.SetCmdRunner(cr)
return cr
}

if err := ParseBytes([]byte(cmdline), &cmd); err != nil {
if _, err := fmt.Fprintf(output, "Error: %v\n", err); err != nil {
return err
func (rt *CmdRunner) RunCommand(cmdline string, output io.Writer) error {
if rt.ctx.Err() == nil {
// if character '!' is used to invoke no-node (global) context, remove it.
if len(cmdline) > 1 && cmdline[0] == '!' {
cmdline = cmdline[1:]
}
} else {
rt.execute(&cmd, output)
}
// run the OTNS-CLI command without node context
cmd := Command{}

return nil
if err := ParseBytes([]byte(cmdline), &cmd); err != nil {
if _, err := fmt.Fprintf(output, "Error: %v\n", err); err != nil {
return err
}
} else {
rt.execute(&cmd, output)
}
}
return rt.ctx.Err()
}

func (rt *CmdRunner) HandleCommand(cmdline string, output io.Writer) error {
if rt.ctx.Err() != nil {
return nil
} else if rt.contextNodeId != InvalidNodeId && !isContextlessCommand(cmdline) {
// run the command in node context
cmd := Command{
Node: &NodeCmd{
Node: NodeSelector{Id: rt.contextNodeId},
Command: &cmdline,
},
if rt.ctx.Err() == nil {
if rt.contextNodeId != InvalidNodeId && !isContextlessCommand(cmdline) {
// run the command in node context
cmd := Command{
Node: &NodeCmd{
Node: NodeSelector{Id: rt.contextNodeId},
Command: &cmdline,
},
}
rt.execute(&cmd, output)
} else {
// run the command without node-specific context
return rt.RunCommand(cmdline, output)
}
rt.execute(&cmd, output)
return nil
} else {
// run the command without node-specific context
return rt.RunCommand(cmdline, output)
}
return rt.ctx.Err()
}

func (rt *CmdRunner) GetPrompt() string {
Expand All @@ -167,8 +177,6 @@ func (rt *CmdRunner) execute(cmd *Command, output io.Writer) {
defer func() {
if cc.Err() != nil {
cc.outputf("Error: %v\n", cc.Err())
} else if rt.ctx.Err() != nil {
cc.outputf("Error: %s\n", simulation.CommandInterruptedError)
} else if !cc.isBackgroundCmd {
cc.outputf("Done\n")
} else {
Expand Down Expand Up @@ -287,7 +295,7 @@ func (rt *CmdRunner) executeGo(cc *CommandContext, cmd *GoCmd) {
})
cc.err = <-done

if rt.ctx.Err() != nil {
if rt.ctx.Err() != nil || cc.err != nil {
break
}
}
Expand All @@ -312,12 +320,7 @@ func (rt *CmdRunner) postAsyncWait(f func(sim *simulation.Simulation)) {
f(rt.sim)
close(done)
})
select {
case <-done:
break
case <-rt.ctx.Done():
break
}
<-done
}

func (rt *CmdRunner) executeAddNode(cc *CommandContext, cmd *AddCmd) {
Expand Down Expand Up @@ -1030,14 +1033,3 @@ func (rt *CmdRunner) executeHelp(cc *CommandContext, cmd *HelpCmd) {
cc.outputStr(rt.help.outputGeneralHelp())
}
}

func NewCmdRunner(ctx *progctx.ProgCtx, sim *simulation.Simulation) *CmdRunner {
cr := &CmdRunner{
ctx: ctx,
sim: sim,
contextNodeId: InvalidNodeId,
help: newHelp(),
}
sim.SetCmdRunner(cr)
return cr
}
58 changes: 40 additions & 18 deletions cli/runcli/runcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"strings"

"github.com/chzyer/readline"
"github.com/simonlingoogle/go-simplelogger"
)

type CliHandler interface {
Expand All @@ -54,8 +55,11 @@ func DefaultCliOptions() *CliOptions {
}
}

// TODO convert into a cli object
var (
readlineInstance *readline.Instance
readlineInstance *readline.Instance = nil
waitCliClosed = make(chan struct{})
Started = make(chan struct{})
)

func RestorePrompt() {
Expand All @@ -64,42 +68,56 @@ func RestorePrompt() {
}
}

func StopCli() {
if readlineInstance != nil {
_ = readlineInstance.Close()
}
}

func RunCli(handler CliHandler, options *CliOptions) error {
func getCliOptions(options *CliOptions) *CliOptions {
if options == nil {
options = DefaultCliOptions()
}

stdin := options.Stdin
if stdin == nil {
stdin = os.Stdin
if options.Stdin == nil {
options.Stdin = os.Stdin
}

stdout := options.Stdout
if stdout == nil {
stdout = os.Stdout
if options.Stdout == nil {
options.Stdout = os.Stdout
}

return options
}

func StopCli(options *CliOptions) {
// cannot call readlineInstance.Close() here, it can block
// (https://github.com/chzyer/readline/issues/217)
options = getCliOptions(options)
// send ETX(Ctrl-C, 0x03, readline.CharInterrupt) to avoid readline internally blocking on Runes() select.
_, _ = options.Stdin.WriteString("\003")
_ = options.Stdin.Close()
simplelogger.Debugf("Waiting for CLI to stop ...")
<-waitCliClosed
simplelogger.Debugf("CLI wait-for-stop done.")
}

func RunCli(handler CliHandler, options *CliOptions) error {
defer close(waitCliClosed)

options = getCliOptions(options)

stdin := options.Stdin
stdinIsTerminal := readline.IsTerminal(int(stdin.Fd()))
if stdinIsTerminal {
stdinState, err := readline.GetState(int(stdin.Fd()))
if err != nil {
close(Started)
return err
}
defer func() {
_ = readline.Restore(int(stdin.Fd()), stdinState)
}()
}

stdout := options.Stdout
stdoutIsTerminal := readline.IsTerminal(int(stdout.Fd()))
if stdoutIsTerminal {
stdoutState, err := readline.GetState(int(stdout.Fd()))
if err != nil {
close(Started)
return err
}
defer func() {
Expand Down Expand Up @@ -134,24 +152,28 @@ func RunCli(handler CliHandler, options *CliOptions) error {
l, err := readline.NewEx(readlineConfig)

if err != nil {
close(Started)
return err
}

defer func() {
_ = l.Close()
}()
readlineInstance = l
close(Started)

for {
// update the prompt and read a line
l.SetPrompt(handler.GetPrompt())
line, err := l.Readline()

if errors.Is(err, readline.ErrInterrupt) {
if len(line) > 0 && line[0] == readline.CharInterrupt {
return nil
} else if errors.Is(err, readline.ErrInterrupt) {
if len(line) == 0 {
return nil
} else {
continue
continue // Ctrl-C in midline edit only cancels the present cmd line.
}
} else if err == io.EOF {
return nil
Expand Down
5 changes: 3 additions & 2 deletions cmd/otns-replay/grpc_service.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2022, The OTNS Authors.
// Copyright (c) 2020-2023, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -32,10 +32,11 @@ import (
"os"
"time"

pb "github.com/openthread/ot-ns/visualize/grpc/pb"
"github.com/pkg/errors"
"github.com/simonlingoogle/go-simplelogger"
"google.golang.org/protobuf/encoding/prototext"

pb "github.com/openthread/ot-ns/visualize/grpc/pb"
)

var (
Expand Down
4 changes: 2 additions & 2 deletions cmd/otns/otns.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020, The OTNS Authors.
// Copyright (c) 2020-2023, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -42,6 +42,6 @@ func main() {
otns_main.Main(ctx, func(ctx *progctx.ProgCtx, args *otns_main.MainArgs) visualize.Visualizer {
return nil
}, nil)
simplelogger.Debugf("OTNS exit.")
simplelogger.Debugf("OTNS main() exit.")
os.Exit(0)
}
26 changes: 26 additions & 0 deletions cmd/real/otns-silk-proxy/otns-silk-proxy.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
// Copyright (c) 2020-2023, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package main

import (
Expand Down
3 changes: 0 additions & 3 deletions dispatcher/FailureCtrl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ func (m mockDispatcherCallback) OnLogMessage(logEntry LogEntry) {
func (m mockDispatcherCallback) OnNextEventTime(curTimeUs uint64, nextTimeUs uint64) {
}

func (m mockDispatcherCallback) OnStop() {
}

func TestFailureCtrlNonFailure(t *testing.T) {
node1 := &Node{
Id: 0x1,
Expand Down
4 changes: 2 additions & 2 deletions dispatcher/Node.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ import (
"fmt"
"net"

"github.com/simonlingoogle/go-simplelogger"

"github.com/openthread/ot-ns/radiomodel"
"github.com/openthread/ot-ns/threadconst"
. "github.com/openthread/ot-ns/types"

"github.com/simonlingoogle/go-simplelogger"
)

const (
Expand Down
Loading

0 comments on commit eb666f3

Please sign in to comment.