feat: P1-3 add heartbeat keep-alive goroutine
- add heartbeat.go with heartbeatIntervalSecs var, SetHeartbeatInterval, runHeartbeat (no build tag, CGO-free) - add heartbeat_test.go with 5 tests covering interval validation and goroutine lifecycle - add StartHeartbeat to clib.go (CGO impl calling xcecli_heartbeat) - remove SetHeartbeatInterval stub from stub.go (now real code in heartbeat.go) - remove TestSetHeartbeatInterval_StubNoOp from stub_funcs_test.go - call SetHeartbeatInterval in OnClusterUpdated to track backend-pushed interval - call backend.StartHeartbeat in cmd/main.go after backend registration
This commit is contained in:
parent
bc3103a6d5
commit
ffc5b20cdb
7 changed files with 125 additions and 12 deletions
80
src/frontend/apiserver/backend/heartbeat_test.go
Normal file
80
src/frontend/apiserver/backend/heartbeat_test.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSetHeartbeatInterval_ValidValue(t *testing.T) {
|
||||
atomic.StoreInt32(&heartbeatIntervalSecs, 30)
|
||||
SetHeartbeatInterval(60)
|
||||
if got := atomic.LoadInt32(&heartbeatIntervalSecs); got != 60 {
|
||||
t.Errorf("heartbeatIntervalSecs = %d, want 60", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHeartbeatInterval_ZeroIgnored(t *testing.T) {
|
||||
atomic.StoreInt32(&heartbeatIntervalSecs, 30)
|
||||
SetHeartbeatInterval(0)
|
||||
if got := atomic.LoadInt32(&heartbeatIntervalSecs); got != 30 {
|
||||
t.Errorf("heartbeatIntervalSecs = %d, want 30 (zero should be ignored)", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHeartbeatInterval_NegativeIgnored(t *testing.T) {
|
||||
atomic.StoreInt32(&heartbeatIntervalSecs, 30)
|
||||
SetHeartbeatInterval(-1)
|
||||
if got := atomic.LoadInt32(&heartbeatIntervalSecs); got != 30 {
|
||||
t.Errorf("heartbeatIntervalSecs = %d, want 30 (negative should be ignored)", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunHeartbeat_CallsFunction(t *testing.T) {
|
||||
atomic.StoreInt32(&heartbeatIntervalSecs, 1)
|
||||
|
||||
var callCount int64
|
||||
fn := func(_ int) {
|
||||
atomic.AddInt64(&callCount, 1)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go runHeartbeat(ctx, fn)
|
||||
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
cancel()
|
||||
|
||||
if got := atomic.LoadInt64(&callCount); got < 1 {
|
||||
t.Errorf("fn was called %d times, want at least 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunHeartbeat_CancelsOnContext(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // already cancelled
|
||||
|
||||
var callCount int64
|
||||
fn := func(_ int) {
|
||||
atomic.AddInt64(&callCount, 1)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
runHeartbeat(ctx, fn)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// returned quickly as expected
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Error("runHeartbeat did not return promptly after context cancellation")
|
||||
}
|
||||
|
||||
if got := atomic.LoadInt64(&callCount); got != 0 {
|
||||
t.Errorf("fn was called %d times, want 0 for already-cancelled context", got)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue