fix(queue): fertige Ansagen nicht mehr als „abgebrochen" protokollieren
Der Worker rief cancel() auf seinem eigenen Kontext, BEVOR er ctx.Err() prüfte — damit war jede normal beendete Ansage im Protokoll „abgebrochen", auch bei Erfolg. Der Abbruch wird jetzt vor dem eigenen cancel() abgelesen. Regressionstest: fertig heißt „fertig nach", ein echter Cancel heißt „abgebrochen". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc26a73aeb
commit
79314dfb0f
2 files changed
+60
-1
No files matched your search
@@ -317,6 +317,9 @@ func (q *Queue) loop() {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := q.speak(ctx, item)
|
err := q.speak(ctx, item)
|
||||||
dur := time.Since(start)
|
dur := time.Since(start)
|
||||||
|
// Abbruch VOR dem eigenen cancel() ablesen — danach wäre ctx.Err()
|
||||||
|
// immer gesetzt und jede fertige Ansage hieße „abgebrochen".
|
||||||
|
cancelled := ctx.Err() != nil
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
q.mu.Lock()
|
q.mu.Lock()
|
||||||
@@ -325,7 +328,7 @@ func (q *Queue) loop() {
|
|||||||
q.mu.Unlock()
|
q.mu.Unlock()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ctx.Err() != nil:
|
case cancelled:
|
||||||
q.logf("queue: ansage %s abgebrochen", item.ID)
|
q.logf("queue: ansage %s abgebrochen", item.ID)
|
||||||
case err != nil:
|
case err != nil:
|
||||||
q.logf("queue: ansage %s fehlgeschlagen nach %s: %v", item.ID, dur.Round(time.Millisecond), err)
|
q.logf("queue: ansage %s fehlgeschlagen nach %s: %v", item.ID, dur.Round(time.Millisecond), err)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -552,3 +554,57 @@ func TestInactiveQuietWindow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// syncLog ist ein nebenläufigkeitssicherer Log-Puffer für Worker-Zeilen.
|
||||||
|
type syncLog struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
b strings.Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *syncLog) Write(p []byte) (int, error) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
return l.b.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *syncLog) String() string {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
return l.b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestProtokollFertigNichtAbgebrochen: Eine normal beendete Ansage wird als
|
||||||
|
// „fertig" protokolliert, nicht als „abgebrochen" — der Worker darf seinen
|
||||||
|
// eigenen cancel() nicht als Abbruch deuten. Ein echter Cancel heißt
|
||||||
|
// dagegen „abgebrochen".
|
||||||
|
func TestProtokollFertigNichtAbgebrochen(t *testing.T) {
|
||||||
|
logb := &syncLog{}
|
||||||
|
g := neueSpielerin()
|
||||||
|
q := New(Laws{}, g.speak, logb)
|
||||||
|
defer q.Close()
|
||||||
|
|
||||||
|
q.Enqueue(Item{Text: "normal zu ende"})
|
||||||
|
g.warteBis(t, 1)
|
||||||
|
g.step <- struct{}{}
|
||||||
|
waitFor(t, func() bool { return strings.Contains(logb.String(), "fertig nach") })
|
||||||
|
if strings.Contains(logb.String(), "abgebrochen") {
|
||||||
|
t.Errorf("fertige Ansage als abgebrochen protokolliert:\n%s", logb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
q.Enqueue(Item{Text: "wird abgebrochen"})
|
||||||
|
g.warteBis(t, 2)
|
||||||
|
q.Cancel()
|
||||||
|
waitFor(t, func() bool { return strings.Contains(logb.String(), "abgebrochen") })
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitFor pollt cond bis zu zwei Sekunden.
|
||||||
|
func waitFor(t *testing.T, cond func() bool) {
|
||||||
|
t.Helper()
|
||||||
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
|
for !cond() {
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
t.Fatal("Bedingung nicht binnen 2 s erfüllt")
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user