github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/sync_batcher.go (about)

     1  package reader
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/Jeffail/benthos/v3/internal/interop"
     9  	"github.com/Jeffail/benthos/v3/lib/log"
    10  	"github.com/Jeffail/benthos/v3/lib/message/batch"
    11  	"github.com/Jeffail/benthos/v3/lib/metrics"
    12  	"github.com/Jeffail/benthos/v3/lib/types"
    13  )
    14  
    15  //------------------------------------------------------------------------------
    16  
    17  // SyncBatcher is a wrapper for reader.Sync implementations that applies a
    18  // batching policy to incoming payloads. Once a batch is created and sent the
    19  // next Acknowledge call applies to all messages of the prior batches.
    20  type SyncBatcher struct {
    21  	batcher *batch.Policy
    22  	r       Sync
    23  	ctx     context.Context
    24  	close   func()
    25  }
    26  
    27  // NewSyncBatcher returns a new SyncBatcher wrapper around a reader.Async.
    28  func NewSyncBatcher(
    29  	batchConfig batch.PolicyConfig,
    30  	r Sync,
    31  	mgr types.Manager,
    32  	log log.Modular,
    33  	stats metrics.Type,
    34  ) (*SyncBatcher, error) {
    35  	mgr, log, stats = interop.LabelChild("batching", mgr, log, stats)
    36  	policy, err := batch.NewPolicy(batchConfig, mgr, log, stats)
    37  	if err != nil {
    38  		return nil, fmt.Errorf("failed to construct batch policy: %v", err)
    39  	}
    40  	ctx, cancel := context.WithCancel(context.Background())
    41  	return &SyncBatcher{
    42  		batcher: policy,
    43  		r:       r,
    44  		ctx:     ctx,
    45  		close:   cancel,
    46  	}, nil
    47  }
    48  
    49  //------------------------------------------------------------------------------
    50  
    51  // Connect attempts to establish a connection to the source, if unsuccessful
    52  // returns an error. If the attempt is successful (or not necessary) returns
    53  // nil.
    54  func (p *SyncBatcher) Connect() error {
    55  	return p.ConnectWithContext(p.ctx)
    56  }
    57  
    58  // ConnectWithContext attempts to establish a connection to the source, if
    59  // unsuccessful returns an error. If the attempt is successful (or not
    60  // necessary) returns nil.
    61  func (p *SyncBatcher) ConnectWithContext(ctx context.Context) error {
    62  	return p.r.ConnectWithContext(ctx)
    63  }
    64  
    65  // Read attempts to read a new message from the source.
    66  func (p *SyncBatcher) Read() (types.Message, error) {
    67  	return p.ReadNextWithContext(p.ctx)
    68  }
    69  
    70  // ReadNextWithContext attempts to read a new message from the source.
    71  func (p *SyncBatcher) ReadNextWithContext(ctx context.Context) (types.Message, error) {
    72  	var forcedBatchDeadline time.Time
    73  	if tout := p.batcher.UntilNext(); tout >= 0 {
    74  		forcedBatchDeadline = time.Now().Add(tout)
    75  		var cancel context.CancelFunc
    76  		ctx, cancel = context.WithDeadline(ctx, forcedBatchDeadline)
    77  		defer cancel()
    78  	}
    79  
    80  	flushBatch := false
    81  	for !flushBatch {
    82  		msg, err := p.r.ReadNextWithContext(ctx)
    83  		if err != nil {
    84  			if !forcedBatchDeadline.IsZero() && !time.Now().Before(forcedBatchDeadline) {
    85  				if batch := p.batcher.Flush(); batch != nil && batch.Len() > 0 {
    86  					return batch, nil
    87  				}
    88  			}
    89  			if err == types.ErrTimeout {
    90  				// If the call "timed out" it could either mean that the context
    91  				// was cancelled, in which case we want to return right now, or
    92  				// that the underlying mechanism timed out, in which case we
    93  				// simply want to try again.
    94  				select {
    95  				case <-ctx.Done():
    96  					if batch := p.batcher.Flush(); batch != nil && batch.Len() > 0 {
    97  						return batch, nil
    98  					}
    99  					return nil, types.ErrTimeout
   100  				default:
   101  				}
   102  				continue
   103  			}
   104  			if err == types.ErrTypeClosed {
   105  				if batch := p.batcher.Flush(); batch != nil && batch.Len() > 0 {
   106  					return batch, nil
   107  				}
   108  			}
   109  			return nil, err
   110  		}
   111  
   112  		msg.Iter(func(i int, part types.Part) error {
   113  			flushBatch = p.batcher.Add(part) || flushBatch
   114  			return nil
   115  		})
   116  	}
   117  
   118  	msg := p.batcher.Flush()
   119  	if msg == nil || msg.Len() == 0 {
   120  		return nil, types.ErrTimeout
   121  	}
   122  	return msg, nil
   123  }
   124  
   125  // Acknowledge confirms whether or not our unacknowledged messages have been
   126  // successfully propagated or not.
   127  func (p *SyncBatcher) Acknowledge(err error) error {
   128  	return p.AcknowledgeWithContext(context.Background(), err)
   129  }
   130  
   131  // AcknowledgeWithContext confirms whether or not our unacknowledged messages
   132  // have been successfully propagated or not.
   133  func (p *SyncBatcher) AcknowledgeWithContext(ctx context.Context, err error) error {
   134  	return p.r.AcknowledgeWithContext(ctx, err)
   135  }
   136  
   137  // CloseAsync triggers the asynchronous closing of the reader.
   138  func (p *SyncBatcher) CloseAsync() {
   139  	p.close()
   140  	p.r.CloseAsync()
   141  }
   142  
   143  // WaitForClose blocks until either the reader is finished closing or a timeout
   144  // occurs.
   145  func (p *SyncBatcher) WaitForClose(tout time.Duration) error {
   146  	tstop := time.Now().Add(tout)
   147  	err := p.r.WaitForClose(time.Until(tstop))
   148  	p.batcher.CloseAsync()
   149  	if err != nil {
   150  		return err
   151  	}
   152  	return p.batcher.WaitForClose(time.Until(tstop))
   153  }
   154  
   155  //------------------------------------------------------------------------------