fix(hints): reap late capture replies while idle
- commit
- 71feaa7f230961e74779acd3ee24af8c7df9e96d
- author
- brice · · 2026-10-19 15:28 UTC
- parent
- 61fd749
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +21 -0
M internal/adapters/hints/hints.go +8 -0
| @@ -81,9 +81,17 @@ func run(ctx context.Context, requests <-chan ports.HintScanRequest, captures ch | |||
| 81 | 81 | select { | |
| 82 | 82 | case <-ctx.Done(): | |
| 83 | 83 | return nil | |
| 84 | + | case done := <-captured: | |
| 85 | + | // A capture abandoned by a timeout is finally done: release it. | |
| 86 | + | w.reap(done.ID) | |
| 84 | 87 | case req := <-requests: | |
| 85 | 88 | res := w.serve(ctx, req, requests, captures, captured, results, timeout) | |
| 86 | 89 | if !send(ctx, results, res) { | |
| 90 | + | // The dump job will never run: release its frame. | |
| 91 | + | if j := w.pending; j != nil { | |
| 92 | + | w.pending = nil | |
| 93 | + | j.m.unmap() | |
| 94 | + | } | |
| 87 | 95 | return nil | |
| 88 | 96 | } | |
| 89 | 97 | w.dump(ctx) | |
| @@ -81,9 +81,17 @@ func run(ctx context.Context, requests <-chan ports.HintScanRequest, captures ch | |||||
| 81 | select { | 81 | select { | ||
| 82 | case <-ctx.Done(): | 82 | case <-ctx.Done(): | ||
| 83 | return nil | 83 | return nil | ||
| 84 | + | case done := <-captured: | |||
| 85 | + | // A capture abandoned by a timeout is finally done: release it. | |||
| 86 | + | w.reap(done.ID) | |||
| 84 | case req := <-requests: | 87 | case req := <-requests: | ||
| 85 | res := w.serve(ctx, req, requests, captures, captured, results, timeout) | 88 | res := w.serve(ctx, req, requests, captures, captured, results, timeout) | ||
| 86 | if !send(ctx, results, res) { | 89 | if !send(ctx, results, res) { | ||
| 90 | + | // The dump job will never run: release its frame. | |||
| 91 | + | if j := w.pending; j != nil { | |||
| 92 | + | w.pending = nil | |||
| 93 | + | j.m.unmap() | |||
| 94 | + | } | |||
| 87 | return nil | 95 | return nil | ||
| 88 | } | 96 | } | ||
| 89 | w.dump(ctx) | 97 | w.dump(ctx) | ||
M internal/adapters/hints/hints_test.go +13 -0
| @@ -418,3 +418,16 @@ func TestNoTesseractDumpStillCaptures(t *testing.T) { | |||
| 418 | 418 | require.NoError(t, (<-h.results).Err) | |
| 419 | 419 | waitFiles(t, dir, 3) | |
| 420 | 420 | } | |
| 421 | + | ||
| 422 | + | // A capture that completes after its scan timed out is released while the | |
| 423 | + | // worker is idle, without another scan, and its reply does not linger. | |
| 424 | + | func TestHintLateCaptureReaped(t *testing.T) { | |
| 425 | + | h := startCapturing(t, 50*time.Millisecond) | |
| 426 | + | h.requests <- ports.HintScanRequest{Seq: 1, Output: "o", Width: 64, Height: 32, Scale: 1} | |
| 427 | + | creq := <-h.captures | |
| 428 | + | res := <-h.results | |
| 429 | + | require.ErrorIs(t, res.Err, ErrTimeout) | |
| 430 | + | // The worker is idle; the output finally answers. | |
| 431 | + | h.captured <- ports.CaptureDone{ID: creq.ID, Output: creq.Output} | |
| 432 | + | require.Eventually(t, func() bool { return len(h.captured) == 0 }, time.Second, time.Millisecond) | |
| 433 | + | } | |
| @@ -418,3 +418,16 @@ func TestNoTesseractDumpStillCaptures(t *testing.T) { | |||||
| 418 | require.NoError(t, (<-h.results).Err) | 418 | require.NoError(t, (<-h.results).Err) | ||
| 419 | waitFiles(t, dir, 3) | 419 | waitFiles(t, dir, 3) | ||
| 420 | } | 420 | } | ||
| 421 | + | ||||
| 422 | + | // A capture that completes after its scan timed out is released while the | |||
| 423 | + | // worker is idle, without another scan, and its reply does not linger. | |||
| 424 | + | func TestHintLateCaptureReaped(t *testing.T) { | |||
| 425 | + | h := startCapturing(t, 50*time.Millisecond) | |||
| 426 | + | h.requests <- ports.HintScanRequest{Seq: 1, Output: "o", Width: 64, Height: 32, Scale: 1} | |||
| 427 | + | creq := <-h.captures | |||
| 428 | + | res := <-h.results | |||
| 429 | + | require.ErrorIs(t, res.Err, ErrTimeout) | |||
| 430 | + | // The worker is idle; the output finally answers. | |||
| 431 | + | h.captured <- ports.CaptureDone{ID: creq.ID, Output: creq.Output} | |||
| 432 | + | require.Eventually(t, func() bool { return len(h.captured) == 0 }, time.Second, time.Millisecond) | |||
| 433 | + | } | |||