fix: harden single-machine load handling

This commit is contained in:
richjohnrambo
2026-09-29 17:52:13 +08:00
parent ab73b69870
commit 4b00b5ae37
24 changed files with 1030 additions and 144 deletions
+54 -8
View File
@@ -458,8 +458,18 @@ func newHistoryQuery(client *WSClient, topic string, messages []AcceptedMessage,
for _, message := range messages {
expected = append(expected, message.ClientMid)
}
return historyQuery{client: client, topic: topic, since: messages[0].Seq,
before: messages[len(messages)-1].Seq + 1, limit: limit, expected: expected}
since := messages[0].Seq
before := messages[len(messages)-1].Seq + 1
// P2P topics interleave both senders. The requested range must leave room
// for non-target messages between the expected sequence numbers.
if span := before - since; span > int64(limit) {
limit = int(span)
}
if limit > 200 {
limit = 200
}
return historyQuery{client: client, topic: topic, since: since,
before: before, limit: limit, expected: expected}
}
func (r *agentRun) waitHistoryPage(ctx context.Context, expected []string, timeout time.Duration) {
@@ -659,13 +669,24 @@ func (r *agentRun) execute(ctx context.Context, start time.Time, groupTopic stri
case "p2p":
return r.scheduleP2P(ctx, start, r.cfg.UsersPerMachine, r.cfg.P2PMessages, mustDuration(r.cfg.P2PInterval))
case "group":
return r.scheduleGroup(ctx, start, groupTopic, 1, 500, 2)
return r.scheduleGroup(ctx, start, groupTopic, 1, scaledUsers(r.cfg.UsersPerMachine, 1, 2), 2)
case "mixed":
p2pUsers := scaledUsers(r.cfg.UsersPerMachine, 3, 10)
groupSenders := scaledUsers(r.cfg.UsersPerMachine, 1, 4)
if p2pUsers+groupSenders > r.cfg.UsersPerMachine {
groupSenders = r.cfg.UsersPerMachine - p2pUsers
}
if groupSenders <= 0 {
return fmt.Errorf("users_per_machine=%d is too small for mixed scenario", r.cfg.UsersPerMachine)
}
var wg sync.WaitGroup
errCh := make(chan error, 2)
wg.Add(2)
go func() { defer wg.Done(); errCh <- r.scheduleP2P(ctx, start, 300, 5, 20*time.Second) }()
go func() { defer wg.Done(); errCh <- r.scheduleGroup(ctx, start, groupTopic, 301, 550, 2) }()
go func() { defer wg.Done(); errCh <- r.scheduleP2P(ctx, start, p2pUsers, 5, 20*time.Second) }()
go func() {
defer wg.Done()
errCh <- r.scheduleGroup(ctx, start, groupTopic, p2pUsers+1, p2pUsers+groupSenders, 2)
}()
wg.Wait()
close(errCh)
for err := range errCh {
@@ -679,6 +700,17 @@ func (r *agentRun) execute(ctx context.Context, start time.Time, groupTopic stri
}
}
func scaledUsers(total, numerator, denominator int) int {
count := total * numerator / denominator
if count < 1 {
return 1
}
if count > total {
return total
}
return count
}
func (r *agentRun) scheduleP2P(ctx context.Context, start time.Time, users, messages int, interval time.Duration) error {
var wg sync.WaitGroup
for index := 1; index <= users; index++ {
@@ -858,9 +890,10 @@ func (r *agentRun) drainDuration() (time.Duration, error) {
func (r *agentRun) close() {
clients := r.clientsSnapshot()
var interval time.Duration
if r.cfg != nil && r.cfg.CloseRamp != "" && len(clients) > 1 {
interval = mustDuration(r.cfg.CloseRamp) / time.Duration(len(clients)-1)
sort.Slice(clients, func(i, j int) bool { return clients[i].account.Index < clients[j].account.Index })
interval, initialDelay := closeSchedule(r.cfg, r.machine, len(clients))
if initialDelay > 0 {
time.Sleep(initialDelay)
}
for index, client := range clients {
client.Close()
@@ -870,6 +903,19 @@ func (r *agentRun) close() {
}
}
func closeSchedule(cfg *Config, machine string, clients int) (interval, initialDelay time.Duration) {
if cfg == nil || cfg.CloseRamp == "" || clients <= 1 {
return 0, 0
}
// The two logical agents share one global ramp. Interleaving their slots avoids
// doubling the disconnect rate when both agents begin cleanup at the same time.
interval = 2 * mustDuration(cfg.CloseRamp) / time.Duration(clients-1)
if machine == "b" {
initialDelay = interval / 2
}
return interval, initialDelay
}
func waitUntil(ctx context.Context, target time.Time) error {
delay := time.Until(target)
if delay <= 0 {