mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-13 02:09:08 +02:00
Stop cleared the done channel, so a waiter that raced a teardown got nil from Done() and selected on a nil channel: an Up racing a Down hung in waitForUp for its full 50s timeout where it previously failed fast with "client gave up to connect". The same clearing made Alive() report a timed-out run dead while its goroutine was still tearing down, letting Up start an overlapping second run. Keep the channel in place instead: the exiting run closes it, so late waiters observe the real exit and Alive() stays true until the run is actually gone — the lifecycle clientGiveUpChan used to have. Stop still drops the current client and invalidates the generation. The stale-running-chan test begins a fresh run before waiting, as Up does, so the kept exit signal does not race the stale clientRunningChan.
134 lines
3.0 KiB
Go
134 lines
3.0 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newSupervisedClient() *ConnectClient {
|
|
return NewConnectClient(context.Background(), nil, nil)
|
|
}
|
|
|
|
func TestRunSupervisorPublishRejectsSupersededRun(t *testing.T) {
|
|
var s RunSupervisor
|
|
ctx := context.Background()
|
|
|
|
staleGen, staleDone := s.Begin()
|
|
defer close(staleDone)
|
|
|
|
freshGen, freshDone := s.Begin()
|
|
defer close(freshDone)
|
|
|
|
fresh := newSupervisedClient()
|
|
require.True(t, s.Publish(ctx, freshGen, fresh))
|
|
|
|
assert.False(t, s.Publish(ctx, staleGen, newSupervisedClient()))
|
|
assert.Same(t, fresh, s.Current())
|
|
}
|
|
|
|
func TestRunSupervisorPublishStopsDisplacedClient(t *testing.T) {
|
|
var s RunSupervisor
|
|
ctx := context.Background()
|
|
|
|
generation, done := s.Begin()
|
|
defer close(done)
|
|
|
|
displaced := newSupervisedClient()
|
|
require.True(t, s.Publish(ctx, generation, displaced))
|
|
|
|
replacement := newSupervisedClient()
|
|
require.True(t, s.Publish(ctx, generation, replacement))
|
|
|
|
assert.Same(t, replacement, s.Current())
|
|
assert.Error(t, displaced.ctx.Err(), "the displaced client's run context should be cancelled")
|
|
}
|
|
|
|
func TestRunSupervisorStopInvalidatesRunInFlight(t *testing.T) {
|
|
var s RunSupervisor
|
|
ctx := context.Background()
|
|
|
|
generation, done := s.Begin()
|
|
close(done)
|
|
|
|
require.NoError(t, s.Stop(ctx))
|
|
|
|
assert.False(t, s.Publish(ctx, generation, newSupervisedClient()))
|
|
assert.Nil(t, s.Current())
|
|
}
|
|
|
|
func TestRunSupervisorStopWaitsForRunExit(t *testing.T) {
|
|
var s RunSupervisor
|
|
|
|
_, done := s.Begin()
|
|
|
|
stopped := make(chan error, 1)
|
|
go func() { stopped <- s.Stop(context.Background()) }()
|
|
|
|
select {
|
|
case <-stopped:
|
|
t.Fatal("Stop returned before the run signalled its exit")
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
|
|
close(done)
|
|
|
|
select {
|
|
case err := <-stopped:
|
|
require.NoError(t, err)
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Stop did not return after the run exited")
|
|
}
|
|
}
|
|
|
|
func TestRunSupervisorStopGivesUpWaitOnContext(t *testing.T) {
|
|
var s RunSupervisor
|
|
|
|
_, done := s.Begin()
|
|
defer close(done)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
|
defer cancel()
|
|
|
|
assert.ErrorIs(t, s.Stop(ctx), context.DeadlineExceeded)
|
|
}
|
|
|
|
func TestRunSupervisorDoneSurvivesStop(t *testing.T) {
|
|
var s RunSupervisor
|
|
|
|
_, done := s.Begin()
|
|
close(done)
|
|
|
|
require.NoError(t, s.Stop(context.Background()))
|
|
|
|
runDone := s.Done()
|
|
require.NotNil(t, runDone, "a waiter that raced Stop must still observe the run's exit")
|
|
select {
|
|
case <-runDone:
|
|
default:
|
|
t.Fatal("the last run's exit signal should remain readable after Stop")
|
|
}
|
|
}
|
|
|
|
func TestRunSupervisorAliveTracksRun(t *testing.T) {
|
|
var s RunSupervisor
|
|
|
|
assert.False(t, s.Alive(), "no run has started")
|
|
|
|
_, done := s.Begin()
|
|
assert.True(t, s.Alive(), "a run is in flight")
|
|
|
|
close(done)
|
|
assert.False(t, s.Alive(), "the run signalled its exit")
|
|
}
|
|
|
|
func TestRunSupervisorStopWithoutRunIsNoop(t *testing.T) {
|
|
var s RunSupervisor
|
|
|
|
require.NoError(t, s.Stop(context.Background()))
|
|
assert.Nil(t, s.Current())
|
|
}
|