fix: keep load agents connected before start
This commit is contained in:
@@ -16,22 +16,27 @@ import (
|
||||
)
|
||||
|
||||
type StartInfo struct {
|
||||
RunID string `json:"run_id"`
|
||||
Scenario string `json:"scenario"`
|
||||
StartAt time.Time `json:"start_at"`
|
||||
RunID string `json:"run_id"`
|
||||
Scenario string `json:"scenario"`
|
||||
StartAt time.Time `json:"start_at"`
|
||||
Committed bool `json:"committed"`
|
||||
}
|
||||
|
||||
type AgentReady struct {
|
||||
Machine string `json:"machine"`
|
||||
ReadyConnections int `json:"ready_connections"`
|
||||
Machine string `json:"machine"`
|
||||
ReadyConnections int `json:"ready_connections"`
|
||||
ExpectedConnections int `json:"expected_connections"`
|
||||
Armed bool `json:"armed"`
|
||||
}
|
||||
|
||||
type coordinatorState struct {
|
||||
mu sync.Mutex
|
||||
start StartInfo
|
||||
startDelay time.Duration
|
||||
commitDelay time.Duration
|
||||
expectedAgents int
|
||||
ready map[string]AgentReady
|
||||
armed map[string]bool
|
||||
ledgers map[string][]AcceptedMessage
|
||||
summaries map[string]AgentSummary
|
||||
complete chan struct{}
|
||||
@@ -49,9 +54,10 @@ func runCoordinator(ctx context.Context, listen, runID, scenario, output string,
|
||||
expectedAgents = 2
|
||||
}
|
||||
state := &coordinatorState{
|
||||
start: StartInfo{RunID: runID, Scenario: scenario}, startDelay: startDelay, expectedAgents: expectedAgents,
|
||||
ready: make(map[string]AgentReady), ledgers: make(map[string][]AcceptedMessage),
|
||||
summaries: make(map[string]AgentSummary), complete: make(chan struct{}),
|
||||
start: StartInfo{RunID: runID, Scenario: scenario}, startDelay: startDelay, commitDelay: 5 * time.Second,
|
||||
expectedAgents: expectedAgents,
|
||||
ready: make(map[string]AgentReady), ledgers: make(map[string][]AcceptedMessage),
|
||||
armed: make(map[string]bool), summaries: make(map[string]AgentSummary), complete: make(chan struct{}),
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/v1/ready", state.handleReady)
|
||||
@@ -91,17 +97,48 @@ func (s *coordinatorState) handleReady(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
var ready AgentReady
|
||||
if !decodeLimitedJSON(w, r, &ready) || (ready.Machine != "a" && ready.Machine != "b") || ready.ReadyConnections <= 0 {
|
||||
if !decodeLimitedJSON(w, r, &ready) || (ready.Machine != "a" && ready.Machine != "b") ||
|
||||
ready.ExpectedConnections <= 0 || ready.ReadyConnections < 0 || ready.ReadyConnections > ready.ExpectedConnections {
|
||||
if ready.Machine != "a" && ready.Machine != "b" {
|
||||
http.Error(w, "invalid machine", http.StatusBadRequest)
|
||||
} else {
|
||||
http.Error(w, "invalid connection counts", http.StatusBadRequest)
|
||||
}
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.ready[ready.Machine] = ready
|
||||
if len(s.ready) == s.expectedAgents && s.start.StartAt.IsZero() {
|
||||
s.start.StartAt = time.Now().UTC().Add(s.startDelay)
|
||||
log.Printf("all agents ready; start_at=%s", s.start.StartAt.Format(time.RFC3339Nano))
|
||||
now := time.Now().UTC()
|
||||
allStable := len(s.ready) == s.expectedAgents
|
||||
for _, agent := range s.ready {
|
||||
if agent.ReadyConnections != agent.ExpectedConnections {
|
||||
allStable = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allStable && (!s.start.Committed || now.Before(s.start.StartAt)) {
|
||||
log.Printf("agent readiness dropped before start; resetting start barrier")
|
||||
s.start.StartAt = time.Time{}
|
||||
s.start.Committed = false
|
||||
clear(s.armed)
|
||||
}
|
||||
if allStable && s.start.StartAt.IsZero() {
|
||||
s.start.StartAt = now.Add(s.startDelay)
|
||||
s.start.Committed = false
|
||||
clear(s.armed)
|
||||
log.Printf("all agents ready; validation_at=%s", s.start.StartAt.Format(time.RFC3339Nano))
|
||||
}
|
||||
if allStable && !s.start.Committed && ready.Armed && !now.Before(s.start.StartAt) {
|
||||
s.armed[ready.Machine] = true
|
||||
}
|
||||
if allStable && !s.start.Committed && len(s.armed) == s.expectedAgents {
|
||||
delay := s.commitDelay
|
||||
if delay <= 0 {
|
||||
delay = 5 * time.Second
|
||||
}
|
||||
s.start.StartAt = now.Add(delay)
|
||||
s.start.Committed = true
|
||||
log.Printf("all agents validated; committed start_at=%s", s.start.StartAt.Format(time.RFC3339Nano))
|
||||
}
|
||||
start := s.start
|
||||
s.mu.Unlock()
|
||||
@@ -185,7 +222,8 @@ func (s *coordinatorState) handleStatus(w http.ResponseWriter, _ *http.Request)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"start": s.start, "ready": s.ready, "ledger_agents": len(s.ledgers), "summary_agents": len(s.summaries),
|
||||
"start": s.start, "ready": s.ready, "armed": s.armed,
|
||||
"ledger_agents": len(s.ledgers), "summary_agents": len(s.summaries),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -232,24 +270,37 @@ func newCoordinatorClient(base string) *coordinatorClient {
|
||||
return &coordinatorClient{base: strings.TrimRight(base, "/"), client: &http.Client{Timeout: 35 * time.Second}}
|
||||
}
|
||||
|
||||
func (c *coordinatorClient) ready(ctx context.Context, ready AgentReady) (StartInfo, error) {
|
||||
func (c *coordinatorClient) ready(ctx context.Context, current func() AgentReady) (StartInfo, error) {
|
||||
var start StartInfo
|
||||
if _, err := c.request(ctx, http.MethodPost, "/v1/ready", ready, &start); err != nil {
|
||||
return start, err
|
||||
}
|
||||
for start.StartAt.IsZero() {
|
||||
armed := false
|
||||
for {
|
||||
state := current()
|
||||
state.Armed = armed
|
||||
if _, err := c.request(ctx, http.MethodPost, "/v1/ready", state, &start); err != nil {
|
||||
return start, err
|
||||
}
|
||||
if start.StartAt.IsZero() {
|
||||
armed = false
|
||||
} else {
|
||||
remaining := time.Until(start.StartAt)
|
||||
if start.Committed && remaining <= 0 {
|
||||
return start, nil
|
||||
}
|
||||
if !start.Committed && remaining <= 0 {
|
||||
armed = true
|
||||
continue
|
||||
}
|
||||
if remaining < time.Second {
|
||||
if err := waitContext(ctx, remaining); err != nil {
|
||||
return start, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := waitContext(ctx, time.Second); err != nil {
|
||||
return start, err
|
||||
}
|
||||
status, err := c.request(ctx, http.MethodGet, "/v1/start", nil, &start)
|
||||
if err != nil {
|
||||
return start, err
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
start = StartInfo{}
|
||||
}
|
||||
}
|
||||
return start, nil
|
||||
}
|
||||
|
||||
func (c *coordinatorClient) submitLedger(ctx context.Context, machine string, messages []AcceptedMessage) error {
|
||||
|
||||
Reference in New Issue
Block a user