github.com/Jeffail/benthos/v3@v3.65.0/internal/transaction/tracked.go (about)

     1  package transaction
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/Jeffail/benthos/v3/internal/batch"
     8  	imessage "github.com/Jeffail/benthos/v3/internal/message"
     9  	"github.com/Jeffail/benthos/v3/lib/response"
    10  	"github.com/Jeffail/benthos/v3/lib/types"
    11  )
    12  
    13  // Tracked is a transaction type that adds identifying tags to messages such
    14  // that an error returned resulting from multiple transaction messages can be
    15  // reduced.
    16  type Tracked struct {
    17  	msg     types.Message
    18  	group   *imessage.SortGroup
    19  	resChan chan<- types.Response
    20  }
    21  
    22  // NewTracked creates a transaction from a message batch and a response channel.
    23  // The message is tagged with an identifier for the transaction, and if an error
    24  // is returned from a downstream component that merged messages from other
    25  // transactions the tag can be used in order to determine whether the message
    26  // owned by this transaction succeeded.
    27  func NewTracked(msg types.Message, resChan chan<- types.Response) *Tracked {
    28  	group, trackedMsg := imessage.NewSortGroup(msg)
    29  	return &Tracked{
    30  		msg:     trackedMsg,
    31  		resChan: resChan,
    32  		group:   group,
    33  	}
    34  }
    35  
    36  // Message returns the message owned by this transaction.
    37  func (t *Tracked) Message() types.Message {
    38  	return t.msg
    39  }
    40  
    41  // ResponseChan returns the response channel owned by this transaction.
    42  func (t *Tracked) ResponseChan() chan<- types.Response {
    43  	return t.resChan
    44  }
    45  
    46  func (t *Tracked) getResFromGroup(walkable batch.WalkableError) types.Response {
    47  	remainingIndexes := make(map[int]struct{}, t.msg.Len())
    48  	for i := 0; i < t.msg.Len(); i++ {
    49  		remainingIndexes[i] = struct{}{}
    50  	}
    51  
    52  	var res types.Response
    53  	walkable.WalkParts(func(_ int, p types.Part, err error) bool {
    54  		if index := t.group.GetIndex(p); index >= 0 {
    55  			if err != nil {
    56  				res = response.NewError(err)
    57  				return false
    58  			}
    59  			delete(remainingIndexes, index)
    60  			if len(remainingIndexes) == 0 {
    61  				return false
    62  			}
    63  		}
    64  		return true
    65  	})
    66  	if res != nil {
    67  		return res
    68  	}
    69  
    70  	if len(remainingIndexes) > 0 {
    71  		return response.NewError(errors.Unwrap(walkable))
    72  	}
    73  	return response.NewAck()
    74  }
    75  
    76  func (t *Tracked) resFromError(err error) types.Response {
    77  	var res types.Response = response.NewAck()
    78  	if err != nil {
    79  		if walkable, ok := err.(batch.WalkableError); ok {
    80  			res = t.getResFromGroup(walkable)
    81  		} else {
    82  			res = response.NewError(err)
    83  		}
    84  	}
    85  	return res
    86  }
    87  
    88  // Ack provides a response to the upstream service from an error.
    89  func (t *Tracked) Ack(ctx context.Context, err error) error {
    90  	select {
    91  	case t.resChan <- t.resFromError(err):
    92  	case <-ctx.Done():
    93  		return context.Canceled
    94  	}
    95  	return nil
    96  }
    97  
    98  //------------------------------------------------------------------------------