Skip to content
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
11 changes: 9 additions & 2 deletions core/nylon.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ func (n *Nylon) Init() error {
return nil
}

var errShutdownSignal = errors.New("received shutdown signal")

func (n *Nylon) Start() error {
n.Log.Info("init modules complete")

Expand All @@ -343,7 +345,7 @@ func (n *Nylon) Start() error {
go func() {
select {
case _ = <-c:
n.Cancel(errors.New("received shutdown signal"))
n.Cancel(errShutdownSignal)
case <-n.Context.Done():
return
}
Expand Down Expand Up @@ -402,7 +404,12 @@ endLoop:
n.Log.Error("error occurred during Stop: ", "error", err)
}
n.Log.Info("stopped")
return nil
// report fatal errors so the process exits non-zero
cause := context.Cause(n.Context)
if errors.Is(cause, context.Canceled) || errors.Is(cause, errShutdownSignal) {
return nil
}
return cause
}

func (n *Nylon) Cleanup() error {
Expand Down
9 changes: 8 additions & 1 deletion integration/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ type Harness struct {
// UntrackedRouting turns off UseSystemRouting on every node, so packets
// for peers stay inside polyamide instead of coming out of the TUN.
UntrackedRouting bool
// OnStartError, if set, receives errors returned by a node's Start
// instead of failing the test.
OnStartError func(id state.NodeId, err error)

ctx context.Context
cancel context.CancelCauseFunc
Expand Down Expand Up @@ -238,7 +241,11 @@ func (h *Harness) launch(nd *node, n *core.Nylon, delay time.Duration) {
}
pprof.Do(context.Background(), pprof.Labels("nylon node", string(n.LocalCfg.Id)), func(context.Context) {
if err := n.Start(); err != nil {
h.fail("node %s: %v", n.LocalCfg.Id, err)
if h.OnStartError != nil {
h.OnStartError(n.LocalCfg.Id, err)
} else {
h.fail("node %s: %v", n.LocalCfg.Id, err)
}
}
})
}()
Expand Down
20 changes: 20 additions & 0 deletions integration/ipc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,23 @@ func TestIPCConcurrentRequestsAndCancel(t *testing.T) {
require.Less(t, time.Since(start), 4*time.Second, "the cancelled probe should end before its timeout")
})
}

// A node stopped by a fatal error must report it so nylon run exits non-zero.
func TestStartReturnsFatalDispatchError(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
h := NewHarness(t)
errs := make(chan error, 2)
h.OnStartError = func(_ state.NodeId, err error) { errs <- err }
a1, b1 := "192.168.54.1:1234", "192.168.54.2:1234"
h.AddNode("a", "10.0.0.1/32", a1)
h.AddNode("b", "10.0.0.2/32", b1)
h.Central.Graph = []string{"a, b"}
h.Connect(a1, b1)
h.Start()
time.Sleep(time.Second) // let both nodes start

h.Nylon("a").Dispatch(func() error { return errors.New("fatal") })
err := await(h, errs, 5*time.Second, "a's Start to return")
require.ErrorContains(t, err, "fatal")
})
}
Loading