收窄 assignment 队列职责

This commit is contained in:
2026-09-20 19:06:04 +00:00
parent 27787d7899
commit a856176c56
5 changed files with 98 additions and 113 deletions
+14 -59
View File
@@ -48,8 +48,8 @@ func (p Publisher) Dispatch(ctx context.Context, assignment taskassignment.Assig
return nil
}
type Handler interface {
Handle(context.Context, taskassignment.Assignment) (bool, error)
type Accepter interface {
Accept(context.Context, taskassignment.Assignment) (bool, error)
}
// Message is the subset of jetstream.Msg needed by one reconciliation.
@@ -57,27 +57,25 @@ type Message interface {
Data() []byte
DoubleAck(context.Context) error
NakWithDelay(time.Duration) error
InProgress() error
TermWithReason(string) error
}
// Processor maps one delivery to one idempotent worker reconciliation.
type Processor struct {
TrustDomain string
Handler Handler
RetryDelay time.Duration
PollInterval time.Duration
TrustDomain string
Accepter Accepter
RetryDelay time.Duration
}
func (p Processor) Process(ctx context.Context, message Message) error {
if p.Handler == nil {
return errors.New("assignment handler is required")
if p.Accepter == nil {
return errors.New("assignment accepter is required")
}
assignment, err := taskassignment.Unmarshal(message.Data(), p.TrustDomain)
if err != nil {
return errors.Join(err, message.TermWithReason("invalid assignment"))
}
done, err := p.Handler.Handle(ctx, assignment)
accepted, err := p.Accepter.Accept(ctx, assignment)
if err != nil {
delay := p.RetryDelay
if delay <= 0 {
@@ -85,60 +83,17 @@ func (p Processor) Process(ctx context.Context, message Message) error {
}
return errors.Join(err, message.NakWithDelay(delay))
}
if done {
if accepted {
if err := message.DoubleAck(ctx); err != nil {
return fmt.Errorf("ack assignment %s: %w", assignment.ID, err)
}
return nil
}
if err := message.InProgress(); err != nil {
return fmt.Errorf("extend assignment %s acknowledgement: %w", assignment.ID, err)
}
return nil
}
// ProcessUntilDone holds one durable delivery while repeatedly reconciling
// backend state. Cancellation leaves it unacknowledged for another process.
func (p Processor) ProcessUntilDone(ctx context.Context, message Message) error {
if p.Handler == nil {
return errors.New("assignment handler is required")
}
assignment, err := taskassignment.Unmarshal(message.Data(), p.TrustDomain)
if err != nil {
return errors.Join(err, message.TermWithReason("invalid assignment"))
}
interval := p.PollInterval
if interval <= 0 {
interval = 2 * time.Second
}
for {
done, handleErr := p.Handler.Handle(ctx, assignment)
if handleErr != nil {
delay := p.RetryDelay
if delay <= 0 {
delay = 15 * time.Second
}
return errors.Join(handleErr, message.NakWithDelay(delay))
}
if done {
if err := message.DoubleAck(ctx); err != nil {
return fmt.Errorf("ack assignment %s: %w", assignment.ID, err)
}
return nil
}
if err := message.InProgress(); err != nil {
return fmt.Errorf("extend assignment %s acknowledgement: %w", assignment.ID, err)
}
timer := time.NewTimer(interval)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return ctx.Err()
case <-timer.C:
}
delay := p.RetryDelay
if delay <= 0 {
delay = 2 * time.Second
}
return message.NakWithDelay(delay)
}
type consumeAPI interface {
@@ -198,7 +153,7 @@ func (c ConsumerComponent) Run(ctx context.Context) error {
go func() {
defer workers.Done()
defer func() { <-semaphore }()
if err := c.Processor.ProcessUntilDone(ctx, message); err != nil && !errors.Is(err, context.Canceled) && c.OnError != nil {
if err := c.Processor.Process(ctx, message); err != nil && !errors.Is(err, context.Canceled) && c.OnError != nil {
c.OnError(err)
}
}()