perf(cmpp): decouple supplier result callbacks
This commit is contained in:
@@ -36,6 +36,7 @@ type Worker struct {
|
||||
Limiter ratelimit.Limiter
|
||||
Submit func(context.Context, queue.SubmitCommand) (queue.SubmitResult, error)
|
||||
ReportDeadLetter func(context.Context, DeadLetterEvent) error
|
||||
ResultOutbox SubmitResultOutbox
|
||||
Stream string
|
||||
Group string
|
||||
Consumer string
|
||||
@@ -50,6 +51,17 @@ type Worker struct {
|
||||
inFlight atomic.Int64
|
||||
}
|
||||
|
||||
type SubmitResultOutbox interface {
|
||||
PublishSubmitResultAndAck(
|
||||
context.Context,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
queue.SubmitCommand,
|
||||
queue.SubmitResult,
|
||||
) error
|
||||
}
|
||||
|
||||
type DeadLetterEvent struct {
|
||||
StreamMessageID string `json:"streamMessageId"`
|
||||
TraceID string `json:"traceId,omitempty"`
|
||||
@@ -82,6 +94,9 @@ func (w *Worker) Run(ctx context.Context) error {
|
||||
if w.Upstream == nil {
|
||||
return fmt.Errorf("upstream manager is required")
|
||||
}
|
||||
if w.ResultOutbox == nil {
|
||||
return fmt.Errorf("submit result Outbox is required")
|
||||
}
|
||||
pool := newMessageWorkPool(ctx, w, w.concurrency())
|
||||
defer pool.wait()
|
||||
for {
|
||||
@@ -292,7 +307,8 @@ func (w *Worker) processMessage(ctx context.Context, message redis.XMessage) err
|
||||
if !command.CreatedAt.IsZero() {
|
||||
metrics.ObserveSubmitStage("stream_wait", true, time.Since(command.CreatedAt))
|
||||
}
|
||||
if err := w.handleCommand(ctx, command); err != nil {
|
||||
result, err := w.executeCommand(ctx, command)
|
||||
if err != nil && (result.SubmitStatus == "" || result.SubmitStatus == "accepted") {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -308,29 +324,50 @@ func (w *Worker) processMessage(ctx context.Context, message redis.XMessage) err
|
||||
}
|
||||
return err
|
||||
}
|
||||
return w.ackAndClearFailure(ctx, message.ID)
|
||||
if err := w.ResultOutbox.PublishSubmitResultAndAck(
|
||||
ctx,
|
||||
w.stream(),
|
||||
w.group(),
|
||||
message.ID,
|
||||
command,
|
||||
result,
|
||||
); err != nil {
|
||||
return fmt.Errorf("persist aggregate submit result: %w", err)
|
||||
}
|
||||
// The command was ACKed atomically with the aggregate result event. Clearing the
|
||||
// auxiliary retry counter may lag without affecting delivery correctness.
|
||||
w.clearFailure(ctx, message.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) handleCommand(ctx context.Context, command queue.SubmitCommand) error {
|
||||
func (w *Worker) executeCommand(ctx context.Context, command queue.SubmitCommand) (queue.SubmitResult, error) {
|
||||
startedAt := time.Now()
|
||||
limitStartedAt := time.Now()
|
||||
if w.Limiter != nil {
|
||||
if _, err := w.Limiter.Wait(ctx, command.ChannelID, command.Route.RateLimitPerSecond); err != nil {
|
||||
metrics.ObserveSubmitStage("rate_limit_wait", false, time.Since(limitStartedAt))
|
||||
return err
|
||||
return queue.SubmitResult{}, err
|
||||
}
|
||||
}
|
||||
metrics.ObserveSubmitStage("rate_limit_wait", true, time.Since(limitStartedAt))
|
||||
submit := w.Submit
|
||||
if submit == nil {
|
||||
if w.Upstream == nil {
|
||||
return fmt.Errorf("upstream manager is required")
|
||||
return queue.SubmitResult{}, fmt.Errorf("upstream manager is required")
|
||||
}
|
||||
submit = w.Upstream.Submit
|
||||
}
|
||||
result, err := submit(ctx, command)
|
||||
accepted := err == nil && (result.SubmitStatus == "" || result.SubmitStatus == "accepted")
|
||||
metrics.ObserveSubmit(accepted, time.Since(startedAt))
|
||||
return result, err
|
||||
}
|
||||
|
||||
// handleCommand remains a narrow compatibility seam for focused tests and the
|
||||
// control path. Stream consumption uses executeCommand so it can persist the
|
||||
// exact terminal result before acknowledging the command.
|
||||
func (w *Worker) handleCommand(ctx context.Context, command queue.SubmitCommand) error {
|
||||
result, err := w.executeCommand(ctx, command)
|
||||
if err != nil && (result.SubmitStatus == "" || result.SubmitStatus == "accepted") {
|
||||
return err
|
||||
}
|
||||
@@ -456,10 +493,14 @@ func (w *Worker) ackAndClearFailure(ctx context.Context, messageID string) error
|
||||
if err := w.Redis.XAck(ctx, w.stream(), w.group(), messageID).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.clearFailure(ctx, messageID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) clearFailure(ctx context.Context, messageID string) {
|
||||
if err := w.Redis.HDel(ctx, w.failureAttemptsKey(), messageID).Err(); err != nil {
|
||||
w.logf("gateway submit worker clear failure %s failed: %v", messageID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) stream() string {
|
||||
|
||||
Reference in New Issue
Block a user