fix: align business reports and record task migration progress
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type outputKey struct{}
|
||||
|
||||
// Output collects bounded execution details without changing the method contract.
|
||||
// A timed-out method may still write, so snapshots and writes must be synchronized.
|
||||
type Output struct {
|
||||
mu sync.Mutex
|
||||
text string
|
||||
truncated bool
|
||||
}
|
||||
|
||||
func WithOutput(ctx context.Context) (context.Context, *Output) {
|
||||
output := &Output{}
|
||||
return context.WithValue(ctx, outputKey{}, output), output
|
||||
}
|
||||
|
||||
// Logf records operational counts and progress. Do not include credentials or
|
||||
// whole request/response bodies. Outside a task execution it is a no-op.
|
||||
func Logf(ctx context.Context, format string, args ...any) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
output, _ := ctx.Value(outputKey{}).(*Output)
|
||||
if output == nil {
|
||||
return
|
||||
}
|
||||
line := fmt.Sprintf(format, args...)
|
||||
output.mu.Lock()
|
||||
defer output.mu.Unlock()
|
||||
const limit = 3000 // Reserve space for the executor's final status.
|
||||
if output.truncated {
|
||||
return
|
||||
}
|
||||
remaining := limit - len(output.text)
|
||||
if len(line)+1 > remaining {
|
||||
cut := max(0, remaining-1)
|
||||
for cut > 0 && !utf8.RuneStart(line[cut]) {
|
||||
cut--
|
||||
}
|
||||
line = line[:cut]
|
||||
output.truncated = true
|
||||
}
|
||||
output.text += line + "\n"
|
||||
}
|
||||
|
||||
func (output *Output) String() string {
|
||||
output.mu.Lock()
|
||||
defer output.mu.Unlock()
|
||||
text := strings.TrimSpace(output.text)
|
||||
if output.truncated {
|
||||
text += "\n...(过程详情已截断)"
|
||||
}
|
||||
return text
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func TestOutputIsBoundedConcurrentAndExecutionLocal(t *testing.T) {
|
||||
ctx, output := WithOutput(context.Background())
|
||||
other, separate := WithOutput(context.Background())
|
||||
Logf(other, "独立任务")
|
||||
var workers sync.WaitGroup
|
||||
for i := 0; i < 20; i++ {
|
||||
workers.Add(1)
|
||||
go func() {
|
||||
defer workers.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
Logf(ctx, "%s", strings.Repeat("渠道", 20))
|
||||
_ = output.String()
|
||||
}
|
||||
}()
|
||||
}
|
||||
workers.Wait()
|
||||
text := output.String()
|
||||
if !utf8.ValidString(text) || len(text) > 3100 || !strings.Contains(text, "已截断") {
|
||||
t.Fatalf("invalid bounded output: bytes=%d", len(text))
|
||||
}
|
||||
if separate.String() != "独立任务" {
|
||||
t.Fatal("execution details leaked between tasks")
|
||||
}
|
||||
Logf(context.Background(), "outside an execution")
|
||||
}
|
||||
Reference in New Issue
Block a user