more fixes!

This commit is contained in:
JackDoan
2026-07-13 18:55:44 -05:00
parent 1aca2f75ae
commit ae17513bbf
5 changed files with 34 additions and 21 deletions
+3 -2
View File
@@ -45,13 +45,14 @@ func (p *program) Start(s service.Service) error {
return err return err
} }
if err := p.control.Start(); err != nil { wait, err := p.control.Start()
if err != nil {
return err return err
} }
// Nebula can stop itself on a fatal packet reader error, make sure to log it if it happens. // Nebula can stop itself on a fatal packet reader error, make sure to log it if it happens.
go func() { go func() {
if err := p.control.Wait(); err != nil { if err := wait(); err != nil {
logger.Error(fmt.Sprintf("Nebula stopped due to fatal error: %v", err)) logger.Error(fmt.Sprintf("Nebula stopped due to fatal error: %v", err))
os.Exit(2) os.Exit(2)
} }
+3 -2
View File
@@ -84,7 +84,8 @@ func main() {
} }
if !*configTest { if !*configTest {
if err := ctrl.Start(); err != nil { wait, err := ctrl.Start()
if err != nil {
util.LogWithContextIfNeeded("Error while running", err, l) util.LogWithContextIfNeeded("Error while running", err, l)
os.Exit(1) os.Exit(1)
} }
@@ -92,7 +93,7 @@ func main() {
go ctrl.ShutdownBlock() go ctrl.ShutdownBlock()
notifyReady(l) notifyReady(l)
if err := ctrl.Wait(); err != nil { if err := wait(); err != nil {
l.Error("Nebula stopped due to fatal error", "error", err) l.Error("Nebula stopped due to fatal error", "error", err)
os.Exit(2) os.Exit(2)
} }
+9 -7
View File
@@ -70,19 +70,21 @@ type ControlHostInfo struct {
} }
// Start actually runs nebula, this is a nonblocking call. // Start actually runs nebula, this is a nonblocking call.
// Use Wait to block until nebula has fully stopped and to learn whether a fatal reader error caused the shutdown. // The returned function blocks until nebula has fully stopped and reports the
func (c *Control) Start() error { // fatal reader error (nil on a clean shutdown). Calling it is equivalent to
// calling Wait.
func (c *Control) Start() (func() error, error) {
c.stateLock.Lock() c.stateLock.Lock()
defer c.stateLock.Unlock() defer c.stateLock.Unlock()
switch c.state { switch c.state {
case StateReady: case StateReady:
//yay! //yay!
case StateStopped, StateStopping: case StateStopped, StateStopping:
return ErrAlreadyStopped return nil, ErrAlreadyStopped
case StateStarted: case StateStarted:
return ErrAlreadyStarted return nil, ErrAlreadyStarted
default: default:
return ErrUnknownState return nil, ErrUnknownState
} }
// Activate the interface // Activate the interface
@@ -92,7 +94,7 @@ func (c *Control) Start() error {
c.cancel() c.cancel()
_ = c.f.Close() _ = c.f.Close()
c.state = StateStopped c.state = StateStopped
return err return nil, err
} }
// Call all the delayed funcs that waited patiently for the interface to be created. // Call all the delayed funcs that waited patiently for the interface to be created.
@@ -120,7 +122,7 @@ func (c *Control) Start() error {
// Start reading packets. // Start reading packets.
c.f.run() c.f.run()
c.state = StateStarted c.state = StateStarted
return nil return c.Wait, nil
} }
func (c *Control) State() RunState { func (c *Control) State() RunState {
+17 -8
View File
@@ -114,7 +114,8 @@ func TestControl_StopBeforeStart(t *testing.T) {
require.NoError(t, c.Wait()) require.NoError(t, c.Wait())
// A stopped control can never be started // A stopped control can never be started
require.ErrorIs(t, c.Start(), ErrAlreadyStopped) _, err := c.Start()
require.ErrorIs(t, err, ErrAlreadyStopped)
// A second Stop is a harmless no-op // A second Stop is a harmless no-op
c.Stop() c.Stop()
@@ -190,7 +191,8 @@ func TestControl_StartMultiqueueFailureReleases(t *testing.T) {
} }
// The second reader fails to open, everything must be released // The second reader fails to open, everything must be released
require.Error(t, c.Start()) _, err := c.Start()
require.Error(t, err)
assert.Equal(t, StateStopped, c.State()) assert.Equal(t, StateStopped, c.State())
assert.True(t, dev.closed, "the tun device should have been closed") assert.True(t, dev.closed, "the tun device should have been closed")
assert.True(t, conn.closed, "the udp socket should have been closed") assert.True(t, conn.closed, "the udp socket should have been closed")
@@ -247,7 +249,7 @@ func TestControl_ConcurrentStopAndStart(t *testing.T) {
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
wg.Go(func() { c.Stop() }) wg.Go(func() { c.Stop() })
} }
wg.Go(func() { _ = c.Start() }) wg.Go(func() { _, _ = c.Start() })
wg.Go(func() { wg.Go(func() {
_ = c.Wait() _ = c.Wait()
// A returned Wait must always observe the final state, no matter how // A returned Wait must always observe the final state, no matter how
@@ -260,15 +262,19 @@ func TestControl_ConcurrentStopAndStart(t *testing.T) {
// panic and Wait must observe the final state // panic and Wait must observe the final state
require.NoError(t, c.Wait()) require.NoError(t, c.Wait())
assert.Equal(t, StateStopped, c.State()) assert.Equal(t, StateStopped, c.State())
require.ErrorIs(t, c.Start(), ErrAlreadyStopped) _, err := c.Start()
require.ErrorIs(t, err, ErrAlreadyStopped)
} }
func TestControl_StartStopLifecycle(t *testing.T) { func TestControl_StartStopLifecycle(t *testing.T) {
c, dev, conn := newReadyControl(t) c, dev, conn := newReadyControl(t)
require.NoError(t, c.Start()) wait, err := c.Start()
require.NoError(t, err)
require.NotNil(t, wait, "a successful Start must return a block function")
assert.Equal(t, StateStarted, c.State()) assert.Equal(t, StateStarted, c.State())
require.ErrorIs(t, c.Start(), ErrAlreadyStarted) _, err = c.Start()
require.ErrorIs(t, err, ErrAlreadyStarted)
// Stop must unpark the reader blocked in the device and release everything // Stop must unpark the reader blocked in the device and release everything
c.Stop() c.Stop()
@@ -279,7 +285,8 @@ func TestControl_StartStopLifecycle(t *testing.T) {
// The reader drained off a closed device, that is not a fatal error // The reader drained off a closed device, that is not a fatal error
require.NoError(t, c.Wait()) require.NoError(t, c.Wait())
require.ErrorIs(t, c.Start(), ErrAlreadyStopped) _, err = c.Start()
require.ErrorIs(t, err, ErrAlreadyStopped)
} }
func TestControl_RebindIsGatedByState(t *testing.T) { func TestControl_RebindIsGatedByState(t *testing.T) {
@@ -289,7 +296,9 @@ func TestControl_RebindIsGatedByState(t *testing.T) {
c.RebindUDPServer() c.RebindUDPServer()
assert.Equal(t, 0, conn.rebinds, "rebind before start must be a no-op") assert.Equal(t, 0, conn.rebinds, "rebind before start must be a no-op")
require.NoError(t, c.Start()) wait, err := c.Start()
require.NoError(t, err)
require.NotNil(t, wait, "a successful Start must return a block function")
c.RebindUDPServer() c.RebindUDPServer()
assert.Equal(t, 1, conn.rebinds, "rebind while started must reach the conn") assert.Equal(t, 1, conn.rebinds, "rebind while started must reach the conn")
+2 -2
View File
@@ -50,7 +50,7 @@ func New(control *nebula.Control) (_ *Service, reterr error) {
return nil, errors.New("must be using user device") return nil, errors.New("must be using user device")
} }
err := control.Start() wait, err := control.Start()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -155,7 +155,7 @@ func New(control *nebula.Control) (_ *Service, reterr error) {
// Add the nebula wait function to the group so a fatal reader error // Add the nebula wait function to the group so a fatal reader error
// propagates out through errgroup.Wait(). // propagates out through errgroup.Wait().
eg.Go(func() error { eg.Go(func() error {
return control.Wait() return wait()
}) })
return &s, nil return &s, nil