04263a17ca
Each virtual player now builds its own edge.Client (its own h2c connection carrying both the Subscribe stream and the Execute calls), instead of every player multiplexing over a single shared http2.Transport. The R2 trip report traced the ~14% transport_error on game.state at 500 players to that single shared transport; per-player connections mirror real clients and isolate the artifact. The assembly burst and the gateway-hammer each get their own client. playTurn now reports when a game has finished so playerLoop drops it from the rotation (slices.DeleteFunc); once no active game remains the player idles while still holding its stream. This stops secondary ops from hammering game_finished on already-ended games (the other R2 harness finding).
48 lines
1.4 KiB
Go
48 lines
1.4 KiB
Go
package scenario
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"scrabble/loadtest/internal/edge"
|
|
"scrabble/loadtest/internal/seed"
|
|
)
|
|
|
|
// HammerConfig parameterises the gateway-hammer: how many concurrent callers and for
|
|
// how long to deliberately exceed the per-user rate limit from a single account.
|
|
type HammerConfig struct {
|
|
Workers int
|
|
Duration time.Duration
|
|
}
|
|
|
|
// DefaultHammer returns a hammer that comfortably exceeds the 300/min per-user limit.
|
|
func DefaultHammer() HammerConfig {
|
|
return HammerConfig{Workers: 20, Duration: 15 * time.Second}
|
|
}
|
|
|
|
// Hammer drives games.list from a single account far above the per-user rate limit to
|
|
// verify the limiter holds — rejections surface as the "rate_limited" code — and to
|
|
// measure its cost. Every call is recorded under "hammer:games.list" so the report
|
|
// shows the ok/rate_limited split and the rejection latency separately from the
|
|
// realistic traffic.
|
|
func (d *Driver) Hammer(ctx context.Context, acc seed.Account, cfg HammerConfig) {
|
|
runCtx, cancel := context.WithTimeout(ctx, cfg.Duration)
|
|
defer cancel()
|
|
d.log.Info("gateway-hammer", "workers", cfg.Workers, "duration", cfg.Duration)
|
|
c := edge.New(d.gateway)
|
|
var wg sync.WaitGroup
|
|
for w := 0; w < cfg.Workers; w++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for runCtx.Err() == nil {
|
|
t0 := time.Now()
|
|
_, code, _ := c.GamesList(runCtx, acc.Token)
|
|
d.rec.Record("hammer:games.list", code, time.Since(t0))
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}
|