Skip to content

Commit 5ac873c

Browse files
committed
Support cancellation through context, let first call through free
1 parent e865b72 commit 5ac873c

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

evaluate/evaluate.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,22 @@ func resolveCacheDir(opts Options, skillDir string) string {
6969
// newThrottle returns a function that blocks until the next request is allowed.
7070
// If rps is 0 or negative, the returned function is a no-op.
7171
// The caller must call the returned stop function when done.
72-
func newThrottle(rps int) (wait func(), stop func()) {
72+
func newThrottle(ctx context.Context, rps int) (wait func(), stop func()) {
7373
if rps <= 0 {
7474
return func() {}, func() {}
7575
}
7676
ticker := time.NewTicker(time.Second / time.Duration(rps))
77-
return func() { <-ticker.C }, ticker.Stop
77+
first := true
78+
return func() {
79+
if first {
80+
first = false
81+
return
82+
}
83+
select {
84+
case <-ticker.C:
85+
case <-ctx.Done():
86+
}
87+
}, ticker.Stop
7888
}
7989

8090
// EvaluateSkill scores a skill directory (SKILL.md and/or reference files).
@@ -89,7 +99,7 @@ func EvaluateSkill(ctx context.Context, dir string, client judge.LLMClient, opts
8999
return nil, fmt.Errorf("loading skill: %w", err)
90100
}
91101

92-
wait, stop := newThrottle(opts.RateLimit)
102+
wait, stop := newThrottle(ctx, opts.RateLimit)
93103
defer stop()
94104

95105
// Score SKILL.md
@@ -252,7 +262,7 @@ func EvaluateSingleFile(ctx context.Context, absPath string, client judge.LLMCli
252262
}
253263
}
254264

255-
wait, stop := newThrottle(opts.RateLimit)
265+
wait, stop := newThrottle(ctx, opts.RateLimit)
256266
defer stop()
257267

258268
wait()

evaluate/evaluate_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,22 @@ func TestEvaluateSkill_RateLimitZeroDisabled(t *testing.T) {
533533
t.Errorf("expected fast completion without rate limit, got %v", elapsed)
534534
}
535535
}
536+
537+
func TestNewThrottle_ContextCancellation(t *testing.T) {
538+
ctx, cancel := context.WithCancel(context.Background())
539+
// 1 req/s = 1s between calls; we should not have to wait that long.
540+
wait, stop := newThrottle(ctx, 1)
541+
defer stop()
542+
543+
// First call is free.
544+
wait()
545+
546+
// Cancel the context, then verify the second call returns promptly
547+
// instead of blocking for the full 1s tick interval.
548+
cancel()
549+
start := time.Now()
550+
wait()
551+
if elapsed := time.Since(start); elapsed > 100*time.Millisecond {
552+
t.Errorf("expected wait to return promptly after context cancellation, took %v", elapsed)
553+
}
554+
}

0 commit comments

Comments
 (0)